Add new API keys to UI without reloading, add small animation, remove html_users.tmpl originaly from unpublished branch

This commit is contained in:
Marc Ole Bulling
2024-12-18 12:02:21 +01:00
parent aebd5a439e
commit e4fe1452fb
6 changed files with 120 additions and 145 deletions

View File

@@ -220,6 +220,16 @@ td.newItem {
}
}
@keyframes subtleHighlightNewApiKey {
0% {
background-color: green; /* Pale green for new items */
}
100% {
background-color: transparent;
}
}
/* Apply the animation to the updated table cells */
.updatedDownloadCount {
animation: subtleHighlight 0.5s ease-out;
@@ -230,4 +240,9 @@ td.newItem {
animation: subtleHighlightNewItem 0.5s ease-out;
}
.newApiKey {
animation: subtleHighlightNewApiKey 0.7s ease-out;
}

View File

@@ -1 +1 @@
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:initial;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastnotification.show{opacity:1;pointer-events:auto}.apiperm-granted{cursor:pointer;color:#19b90e}.apiperm-notgranted{cursor:pointer;color:#7e7e7e}.apiperm-processing{color:#e5eb00}.gokapi-dialog{background-color:#212529;color:#ddd}td.newItem{background-color:green}@keyframes subtleHighlight{0%{background-color:#444950}100%{background-color:initial}}@keyframes subtleHighlightNewItem{0%{background-color:#a8e6a3}100%{background-color:green}}.updatedDownloadCount{animation:subtleHighlight .5s ease-out}.updatedDownloadCount.newItem{animation:subtleHighlightNewItem .5s ease-out}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:initial;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastnotification.show{opacity:1;pointer-events:auto}.apiperm-granted{cursor:pointer;color:#19b90e}.apiperm-notgranted{cursor:pointer;color:#7e7e7e}.apiperm-processing{color:#e5eb00}.gokapi-dialog{background-color:#212529;color:#ddd}td.newItem{background-color:green}@keyframes subtleHighlight{0%{background-color:#444950}100%{background-color:initial}}@keyframes subtleHighlightNewItem{0%{background-color:#a8e6a3}100%{background-color:green}}@keyframes subtleHighlightNewApiKey{0%{background-color:green}100%{background-color:initial}}.updatedDownloadCount{animation:subtleHighlight .5s ease-out}.updatedDownloadCount.newItem{animation:subtleHighlightNewItem .5s ease-out}.newApiKey{animation:subtleHighlightNewApiKey .7s ease-out}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}

View File

@@ -278,52 +278,6 @@ function requestFileInfo(fileId, uid) {
}
function addFriendlyNameChange() {
document.querySelectorAll(".apiname").forEach(function(node) {
node.onclick = function() {
if (this.classList.contains("isBeingEdited"))
return;
this.classList.add("isBeingEdited");
var val = this.innerHTML;
var input = document.createElement("input");
input.size = 5;
input.value = val;
let row = this;
let allowEdit = true;
let submitEntry = function() {
if (!allowEdit)
return;
allowEdit = false;
var newName = input.value;
input.parentNode.innerHTML = newName;
row.classList.remove("isBeingEdited");
apiAuthFriendlyName(row.id, newName)
.catch(error => {
alert("Unable to save name: " + error);
console.error('Error:', error);
});
};
input.onblur = submitEntry;
input.addEventListener("keyup", function(event) {
// Enter
if (event.keyCode === 13) {
event.preventDefault();
submitEntry();
}
});
this.innerHTML = "";
this.appendChild(input);
input.focus();
};
});
}
function parseProgressStatus(eventData) {
let container = document.getElementById(`us-container-${eventData.chunk_id}`);
if (container == null) {
@@ -601,12 +555,11 @@ function deleteApiKey(apiKey) {
function newApiKey() {
document.getElementById("button-newapi").disabled = true;
apiAuthCreate()
.then(data => {
location.reload();
addRowApi(data.Id);
document.getElementById("button-newapi").disabled = false;
})
.catch(error => {
alert("Unable to create API key: " + error);
@@ -615,6 +568,99 @@ function newApiKey() {
}
function addFriendlyNameChange(apiKey) {
let cell = document.getElementById("friendlyname-" + apiKey);
if (cell.classList.contains("isBeingEdited"))
return;
cell.classList.add("isBeingEdited");
let currentName = cell.innerHTML;
let input = document.createElement("input");
input.size = 5;
input.value = currentName;
let allowEdit = true;
let submitEntry = function() {
if (!allowEdit)
return;
allowEdit = false;
let newName = input.value;
cell.innerHTML = newName;
cell.classList.remove("isBeingEdited");
apiAuthFriendlyName(apiKey, newName)
.catch(error => {
alert("Unable to save name: " + error);
console.error('Error:', error);
});
};
input.onblur = submitEntry;
input.addEventListener("keyup", function(event) {
// Enter
if (event.keyCode === 13) {
event.preventDefault();
submitEntry();
}
});
cell.innerHTML = "";
cell.appendChild(input);
input.focus();
}
function addRowApi(apiKey) {
let table = document.getElementById("apitable");
let row = table.insertRow(0);
row.id = "row-" + apiKey;
let cellFriendlyName = row.insertCell(0);
let cellId = row.insertCell(1);
let cellLastUsed = row.insertCell(2);
let cellPermissions = row.insertCell(3);
let cellButtons = row.insertCell(4);
let cellEmpty = row.insertCell(5);
cellFriendlyName.classList.add("newApiKey");
cellId.classList.add("newApiKey");
cellLastUsed.classList.add("newApiKey");
cellPermissions.classList.add("newApiKey");
cellButtons.classList.add("newApiKey");
cellEmpty.classList.add("newApiKey");
cellFriendlyName.innerText = "Unnamed key";
cellFriendlyName.id = "friendlyname-" + apiKey;
cellFriendlyName.onclick = function() {
addFriendlyNameChange(apiKey);
};
cellId.innerText = apiKey;
cellLastUsed.innerText = "Never";
cellButtons.innerHTML = '<button type="button" data-clipboard-text="' + apiKey + '" onclick="showToast()" title="Copy API Key" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i></button> <button id="delete-' + apiKey + '" type="button" class="btn btn-outline-danger btn-sm" onclick="deleteApiKey(\'' + apiKey + '\')" title="Delete"><i class="bi bi-trash3"></i></button>';
cellPermissions.innerHTML = `
<i id="perm_view_` + apiKey + `" class="bi bi-eye apiperm-granted" title="List Uploads" onclick='changeApiPermission("` + apiKey + `","PERM_VIEW", "perm_view_` + apiKey + `");'></i>
<i id="perm_upload_` + apiKey + `" class="bi bi-file-earmark-arrow-up apiperm-granted" title="Upload" onclick='changeApiPermission("` + apiKey + `","PERM_UPLOAD", "perm_upload_` + apiKey + `");'></i>
<i id="perm_edit_` + apiKey + `" class="bi bi-pencil apiperm-granted" title="Edit Uploads" onclick='changeApiPermission("` + apiKey + `","PERM_EDIT", "perm_edit_` + apiKey + `");'></i>
<i id="perm_delete_` + apiKey + `" class="bi bi-trash3 apiperm-granted" title="Delete Uploads" onclick='changeApiPermission("` + apiKey + `","PERM_DELETE", "perm_delete_` + apiKey + `");'></i>
<i id="perm_replace_` + apiKey + `" class="bi bi-recycle apiperm-notgranted" title="Replace Uploads" onclick='changeApiPermission("` + apiKey + `","PERM_REPLACE", "perm_replace_` + apiKey + `");'></i>
<i id="perm_api_` + apiKey + `" class="bi bi-sliders2 apiperm-notgranted" title="Manage API Keys" onclick='changeApiPermission("` + apiKey + `","PERM_API_MOD", "perm_api_` + apiKey + `");'></i>`;
setTimeout(() => {
cellFriendlyName.classList.remove("newApiKey");
cellId.classList.remove("newApiKey");
cellLastUsed.classList.remove("newApiKey");
cellPermissions.classList.remove("newApiKey");
cellButtons.classList.remove("newApiKey");
cellEmpty.classList.remove("newApiKey");
}, 700);
}
function deleteFile(id) {
document.getElementById("button-delete-" + id).disabled = true;

File diff suppressed because one or more lines are too long

View File

@@ -21,11 +21,11 @@
<th scope="col"><button id="button-newapi" type="button" class="btn btn-outline-light btn-sm" onclick="newApiKey()"><i class="bi bi-plus-circle-fill"></i> New Key</button></th>
</tr>
</thead>
<tbody>
<tbody id="apitable">
{{ range .ApiKeys }}
{{ if not .IsSystemKey }}
<tr id="row-{{ .Id }}">
<td scope="col" id="{{ .Id }}" class="apiname">{{ .FriendlyName }}</td>
<td scope="col" id="friendlyname-{{ .Id }}" onClick="addFriendlyNameChange('{{ .Id }}')">{{ .FriendlyName }}</td>
<td scope="col">{{ .Id }}</td>
<td scope="col">{{ .GetReadableDate }}</td>
<td scope="col">
@@ -52,7 +52,6 @@
</div>
<script src="./js/min/admin.min.{{ template "js_admin_version"}}.js"></script>
<script>
addFriendlyNameChange();
var systemKey = "{{.SystemKey}}";
</script>
{{ template "footer" true }}

View File

@@ -1,91 +0,0 @@
{{ define "users" }}
{{ template "header" . }}
<div class="row">
<div class="col">
<div id="container" class="card" style="width: 80%">
<div class="card-body">
<h3 class="card-title">API Keys</h3>
<br>
Please visit the <a target="_blank" href="./apidocumentation">API documentation</a> for more information about the API.<br>Click on the API key name to give it a new name. Permissions can be changed by clicking on them.
<br>
<br>
<div class="table-responsive">
<table class="table table-dark">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Group</th>
<th scope="col">Permissions</th>
<th scope="col">Actions</th>
<th scope="col"><button id="button-newapi" type="button" class="btn btn-outline-light btn-sm" onclick="newApiKey()"><i class="bi bi-plus-circle-fill"></i> New Key</button></th>
</tr>
</thead>
<tbody>
{{ range .Users }}
<tr id="row-{{ .Id }}">
<td scope="col" id="{{ .Id }}">{{ .Name }}</td>
<td scope="col">{{ .Email }}</td>
<td scope="col">{{ .UserLevel }}</td>
<td>
</td>
<td scope="col"><button type="button" data-clipboard-text="{{ .Id }}" onclick="showToast()" title="Copy API Key" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i></button> <button id="delete-{{ .Id }}" type="button" class="btn btn-outline-danger btn-sm" onclick="deleteApiKey('{{ .Id }}')" title="Delete"><i class="bi bi-trash3"></i></button></td>
<td scope="col"></td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
<div id="toastnotification" class="toastnotification">API key copied to clipboard</div>
</div>
</div>
<script src="./js/min/admin.min.{{ template "js_admin_version"}}.js"></script>
<script>
document.querySelectorAll(".apiname").forEach(function(node) {
node.onclick = function() {
if (this.classList.contains("isBeingEdited"))
return;
this.classList.add("isBeingEdited");
var val = this.innerHTML;
var input = document.createElement("input");
input.size = 5;
input.value = val;
let row = this;
let allowEdit = true;
let submitEntry = function() {
if (!allowEdit)
return;
allowEdit = false;
var val = input.value;
input.parentNode.innerHTML = val;
let xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "./api/auth/friendlyname");
xmlhttp.setRequestHeader("apiKeyToModify", row.id);
xmlhttp.setRequestHeader("friendlyName", val);
xmlhttp.setRequestHeader("apikey", systemKey);
xmlhttp.send();
row.classList.remove("isBeingEdited");
//xmlhttp.onreadystatechange = (e) => {
//}
}
input.onblur = submitEntry;
input.addEventListener("keyup", function(event) {
//Enter
if (event.keyCode === 13) {
event.preventDefault();
submitEntry();
}
});
this.innerHTML = "";
this.appendChild(input);
input.focus();
}
});
var systemKey = "{{.SystemKey}}";
</script>
{{ template "footer" true }}
{{ end }}