Add api key management and creation

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD 2026-01-25 13:26:41 -07:00
parent 7ba06e01e4
commit a15b898fa2
9 changed files with 177 additions and 35 deletions

View File

@ -340,7 +340,12 @@ namespace CarCareTracker.Controllers
public IActionResult GetUserAPIKeys()
{
var result = _userLogic.GetAPIKeysByUserId(GetUserID());
return Json(result);
return PartialView("_UserApiKeysModal", result);
}
[HttpGet]
public IActionResult GetCreateApiKeyModal()
{
return PartialView("_CreateApiKeyModal");
}
[HttpPost]
public IActionResult CreateAPIKeyForUser(string keyName, List<HouseholdPermission> permissions)

View File

@ -202,6 +202,18 @@
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="createApiKeyModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" id="createApiKeyModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="userApiKeyModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" id="userApiKeyModalContent">
</div>
</div>
</div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script>

View File

@ -35,6 +35,7 @@
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning me-auto" onclick="showUserApiKeyModalFromUserModal()">@translator.Translate(userLanguage, "Manage API Keys")</button>
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="validateAndSaveUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
</div>

View File

@ -0,0 +1,30 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="createApiKeyLabel">@translator.Translate(userLanguage, "Create API Key")</h5>
<button type="button" class="btn-close" onclick="hideCreateApiKeyModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline">
<div class="form-group">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="inputApiKeyName">@translator.Translate(userLanguage, "Name")</label>
<input type="text" id="inputApiKeyName" class="form-control" placeholder="@translator.Translate(userLanguage, "Name for API Key")">
<label for="inputApiKeyRole">@translator.Translate(userLanguage, "Role")</label>
<select class="form-select" id="inputApiKeyRole">
<!option value="viewer">@translator.Translate(userLanguage, "Viewer")</!option>
<!option value="editor">@translator.Translate(userLanguage, "Editor")</!option>
<!option value="manager">@translator.Translate(userLanguage, "Manager")</!option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideCreateApiKeyModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="createApiKey()" class="btn btn-primary">@translator.Translate(userLanguage, "Create")</button>
</div>

View File

@ -26,6 +26,7 @@
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning me-auto" onclick="showUserApiKeyModalFromUserModal()">@translator.Translate(userLanguage, "Manage API Keys")</button>
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="validateAndSaveRootUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
</div>

View File

@ -0,0 +1,60 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<APIKey>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<div class="d-flex align-items-center">
<h5 class="modal-title">@translator.Translate(userLanguage, "Manage API Keys")</h5>
</div>
<div class="d-flex align-items-center ms-auto">
<button onclick="showCreateApiKeyModal()" class="btn btn-primary">
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add API Key")
</button>
<button type="button" class="btn-close ms-2" onclick="hideUserApiKeyModal()" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
@if (Model.Any())
{
<div style="max-height:30vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Name")</th>
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Role")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Remove")</th>
</tr>
</thead>
<tbody>
@foreach (APIKey viewModel in Model)
{
<tr class="d-flex">
<td class="col-6">@viewModel.Name</td>
<td class="col-4">
@if (!viewModel.Permissions.Contains(HouseholdPermission.Edit) && !viewModel.Permissions.Contains(HouseholdPermission.Delete))
{
@translator.Translate(userLanguage, "Viewer")
}
else if (viewModel.Permissions.Contains(HouseholdPermission.Edit) && !viewModel.Permissions.Contains(HouseholdPermission.Delete))
{
@translator.Translate(userLanguage, "Editor")
}
else if (viewModel.Permissions.Contains(HouseholdPermission.Edit) && viewModel.Permissions.Contains(HouseholdPermission.Delete))
{
@translator.Translate(userLanguage, "Manager")
}
</td>
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="deleteApiKey(@viewModel.Id)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>

File diff suppressed because one or more lines are too long

View File

@ -661,34 +661,67 @@ function addUserToHousehold() {
});
}
function showUserApiKeyModalFromUserModal() {
$('#accountInformationModal').modal('hide');
showUserApiKeyModal();
}
function showUserApiKeyModal() {
$.get('/Home/GetUserAPIKeys', function (data) {
$('#userApiKeyModalContent').html(data);
$("#userApiKeyModal").modal('show');
});
}
function hideUserApiKeyModal() {
$('#userApiKeyModal').modal('hide');
}
function showCreateApiKeyModal() {
$.get('/Home/GetCreateApiKeyModal', function (data) {
$('#createApiKeyModalContent').html(data);
hideUserApiKeyModal();
$("#createApiKeyModal").modal('show');
});
}
function hideCreateApiKeyModal() {
$("#createApiKeyModal").modal('hide');
showUserApiKeyModal();
}
function createApiKey() {
Swal.fire({
title: 'Create API Key',
html: `
<input type="text" id="inputApiKeyName" class="swal2-input" placeholder="Key Name" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Create',
focusConfirm: false,
preConfirm: () => {
const keyName = $("#inputApiKeyName").val();
if (!keyName) {
Swal.showValidationMessage(`Please enter a name`);
}
return { keyName }
},
}).then(function (result) {
if (result.isConfirmed) {
$.post('/Home/CreateAPIKeyForUser', { keyName: result.value.keyName, permissions: [] }, function (data) {
if (data.success) {
Swal.fire({
title: data.message,
icon: 'success',
html: `<div class="input-group"><input type="text" class="form-control" readonly value="${data.additionalData.apiKey}"><div class="input-group-text"><button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="copyApiKey(this)"><i class="bi bi-copy"></i></button></div></div>`
})
} else {
errorToast(data.message);
}
});
let apiKeyName = $("#inputApiKeyName").val();
let apiKeyRole = $("#inputApiKeyRole").val();
//validate
if (apiKeyName.trim() == '') {
$("#inputApiKeyName").addClass('is-invalid');
return;
}
else {
$("#inputApiKeyName").removeClass('is-invalid');
}
let permissions = [];
switch (apiKeyRole) {
case 'editor':
permissions.push('Edit');
break;
case 'manager':
permissions.push('Edit');
permissions.push('Delete');
break;
}
$.post('/Home/CreateAPIKeyForUser', { keyName: apiKeyName, permissions: permissions }, function (data) {
if (data.success) {
$("#createApiKeyModal").modal('hide');
showUserApiKeyModal();
Swal.fire({
title: data.message,
icon: 'success',
html: `<div class="input-group"><input type="text" class="form-control" readonly value="${data.additionalData.apiKey}"><div class="input-group-text"><button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="copyApiKey(this)"><i class="bi bi-copy"></i></button></div></div>`
})
} else {
errorToast(data.message);
}
});
}
@ -698,16 +731,11 @@ function copyApiKey(elem) {
Swal.showValidationMessage(`API Key Copied to Clipboard`);
}
function getUserApiKeys() {
$.get('/Home/GetUserAPIKeys', function (data) {
console.log(data);
})
}
function deleteApiKey(keyId) {
$.post('/Home/DeleteAPIKeyForUser', { keyId: keyId }, function (data) {
if (data.success) {
successToast(data.message);
showUserApiKeyModal();
} else {
errorToast(data.message);
}

View File

@ -74,6 +74,11 @@ function executeAPIEndpoint(sender) {
if (hasError) {
return;
}
let currentParams = new URLSearchParams(window.location.search);
let apiKey = currentParams.get('apiKey');
if (apiKey != null) {
apiPath = `${apiPath}?apiKey=${apiKey}`;
}
let ajaxConfig = {
url: apiPath,
type: apiMethodType,