localized htmx. fixed images/volumes/networks
added hidden checkbox so forms always return an array
This commit is contained in:
parent
377ba6ae67
commit
13ee350bb2
12 changed files with 513 additions and 26 deletions
|
@ -16,6 +16,10 @@ export const Images = async function(req, res) {
|
|||
<th><button class="table-sort" data-sort="sort-quantity">Size</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
</tr>
|
||||
<!-- Hidden checkbox so that the form returns an array each time -->
|
||||
<tr class="d-none">
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="on" type="checkbox" checked="" aria-label="Select"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
||||
|
@ -33,7 +37,7 @@ export const Images = async function(req, res) {
|
|||
<td><input class="form-check-input m-0 align-middle" name="select" value="${images[i].Id}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-name">${images[i].RepoTags}</td>
|
||||
<td class="sort-city">${images[i].Id}</td>
|
||||
<td class="sort-type">Latest</td>
|
||||
<td class="sort-type"> - </td>
|
||||
<td class="sort-score text-green"> - </td>
|
||||
<td class="sort-date" data-date="1628122643">${created}</td>
|
||||
<td class="sort-quantity">${size} MB</td>
|
||||
|
@ -58,7 +62,6 @@ export const Images = async function(req, res) {
|
|||
|
||||
|
||||
export const removeImage = async function(req, res) {
|
||||
|
||||
let images = req.body.select;
|
||||
|
||||
console.log(images);
|
||||
|
@ -75,6 +78,5 @@ export const removeImage = async function(req, res) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect("/images");
|
||||
}
|
|
@ -15,6 +15,10 @@ export const Networks = async function(req, res) {
|
|||
<th><button class="table-sort" data-sort="sort-date">Created</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
</tr>
|
||||
<!-- Hidden checkbox so that the form returns an array each time -->
|
||||
<tr class="d-none">
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="on" type="checkbox" checked="" aria-label="Select"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
||||
|
@ -38,7 +42,6 @@ export const Networks = async function(req, res) {
|
|||
|
||||
network_list += `</tbody>`
|
||||
|
||||
|
||||
res.render("networks", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
|
@ -46,16 +49,13 @@ export const Networks = async function(req, res) {
|
|||
network_list: network_list,
|
||||
network_count: networks.length
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const removeNetwork = async function(req, res) {
|
||||
|
||||
let networks = req.body.select;
|
||||
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
|
||||
if (networks[i] != 'on') {
|
||||
|
@ -68,6 +68,5 @@ export const removeNetwork = async function(req, res) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect("/networks");
|
||||
}
|
9
controllers/variables.js
Normal file
9
controllers/variables.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
export const Variables = (req, res) => {
|
||||
|
||||
res.render("variables", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
}
|
|
@ -17,6 +17,10 @@ export const Volumes = async function(req, res) {
|
|||
<th><button class="table-sort" data-sort="sort-quantity">Size</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
</tr>
|
||||
<!-- Hidden checkbox so that the form returns an array each time -->
|
||||
<tr class="d-none">
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="on" type="checkbox" checked="" aria-label="Select"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
||||
|
@ -50,7 +54,7 @@ export const Volumes = async function(req, res) {
|
|||
<td class="sort-score text-green"> - </td>
|
||||
<td class="sort-date" data-date="1628122643">${volume.CreatedAt}</td>
|
||||
<td class="sort-quantity">MB</td>
|
||||
<td class="text-end"><a class="btn" href="#" disabled="">Details</a></td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
</tr>`
|
||||
|
||||
volume_list += details;
|
||||
|
@ -71,21 +75,20 @@ export const Volumes = async function(req, res) {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const removeVolume = async function(req, res) {
|
||||
|
||||
let volumes = req.body.select;
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
let volume = docker.getVolume(volumes[i]);
|
||||
try {
|
||||
volume.remove();
|
||||
} catch (error) {
|
||||
console.log(`Unable to remove volume ${volumes[i]}`);
|
||||
|
||||
if (volumes[i] != 'on') {
|
||||
try {
|
||||
console.log(`Removing volume: ${volumes[i]}`);
|
||||
let volume = docker.getVolume(volumes[i]);
|
||||
await volume.remove();
|
||||
} catch (error) {
|
||||
console.log(`Unable to remove volume: ${volumes[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect("/volumes");
|
||||
}
|
355
public/js/htmx-sse.js
Normal file
355
public/js/htmx-sse.js
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
Server Sent Events Extension
|
||||
============================
|
||||
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
|
||||
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
/** @type {import("../htmx").HtmxInternalApi} */
|
||||
var api;
|
||||
|
||||
htmx.defineExtension("sse", {
|
||||
|
||||
/**
|
||||
* Init saves the provided reference to the internal HTMX API.
|
||||
*
|
||||
* @param {import("../htmx").HtmxInternalApi} api
|
||||
* @returns void
|
||||
*/
|
||||
init: function(apiRef) {
|
||||
// store a reference to the internal API.
|
||||
api = apiRef;
|
||||
|
||||
// set a function in the public API for creating new EventSource objects
|
||||
if (htmx.createEventSource == undefined) {
|
||||
htmx.createEventSource = createEventSource;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* onEvent handles all events passed to this extension.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Event} evt
|
||||
* @returns void
|
||||
*/
|
||||
onEvent: function(name, evt) {
|
||||
|
||||
switch (name) {
|
||||
|
||||
case "htmx:beforeCleanupElement":
|
||||
var internalData = api.getInternalData(evt.target)
|
||||
// Try to remove remove an EventSource when elements are removed
|
||||
if (internalData.sseEventSource) {
|
||||
internalData.sseEventSource.close();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
// Try to create EventSources when elements are processed
|
||||
case "htmx:afterProcessNode":
|
||||
ensureEventSourceOnElement(evt.target);
|
||||
registerSSE(evt.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
///////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* createEventSource is the default method for creating new EventSource objects.
|
||||
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns EventSource
|
||||
*/
|
||||
function createEventSource(url) {
|
||||
return new EventSource(url, { withCredentials: true });
|
||||
}
|
||||
|
||||
function splitOnWhitespace(trigger) {
|
||||
return trigger.trim().split(/\s+/);
|
||||
}
|
||||
|
||||
function getLegacySSEURL(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
if (legacySSEValue) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "connect") {
|
||||
return value[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLegacySSESwaps(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
var returnArr = [];
|
||||
if (legacySSEValue != null) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "swap") {
|
||||
returnArr.push(value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* registerSSE looks for attributes that can contain sse events, right
|
||||
* now hx-trigger and sse-swap and adds listeners based on these attributes too
|
||||
* the closest event source
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
*/
|
||||
function registerSSE(elt) {
|
||||
// Find closest existing event source
|
||||
var sourceElement = api.getClosestMatch(elt, hasEventSource);
|
||||
if (sourceElement == null) {
|
||||
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||
return null; // no eventsource in parentage, orphaned element
|
||||
}
|
||||
|
||||
// Set internalData and source
|
||||
var internalData = api.getInternalData(sourceElement);
|
||||
var source = internalData.sseEventSource;
|
||||
|
||||
// Add message handlers for every `sse-swap` attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
|
||||
|
||||
var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
|
||||
if (sseSwapAttr) {
|
||||
var sseEventNames = sseSwapAttr.split(",");
|
||||
} else {
|
||||
var sseEventNames = getLegacySSESwaps(child);
|
||||
}
|
||||
|
||||
for (var i = 0; i < sseEventNames.length; i++) {
|
||||
var sseEventName = sseEventNames[i].trim();
|
||||
var listener = function(event) {
|
||||
|
||||
// If the source is missing then close SSE
|
||||
if (maybeCloseSSESource(sourceElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the body no longer contains the element, remove the listener
|
||||
if (!api.bodyContains(child)) {
|
||||
source.removeEventListener(sseEventName, listener);
|
||||
}
|
||||
|
||||
// swap the response into the DOM and trigger a notification
|
||||
swap(child, event.data);
|
||||
api.triggerEvent(elt, "htmx:sseMessage", event);
|
||||
};
|
||||
|
||||
// Register the new listener
|
||||
api.getInternalData(child).sseEventListener = listener;
|
||||
source.addEventListener(sseEventName, listener);
|
||||
}
|
||||
});
|
||||
|
||||
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
|
||||
|
||||
var sseEventName = api.getAttributeValue(child, "hx-trigger");
|
||||
if (sseEventName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process hx-triggers for events with the "sse:" prefix
|
||||
if (sseEventName.slice(0, 4) != "sse:") {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the sse: prefix from here on out
|
||||
sseEventName = sseEventName.substr(4);
|
||||
|
||||
var listener = function() {
|
||||
if (maybeCloseSSESource(sourceElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!api.bodyContains(child)) {
|
||||
source.removeEventListener(sseEventName, listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
|
||||
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
|
||||
* is created and stored in the element's internalData.
|
||||
* @param {HTMLElement} elt
|
||||
* @param {number} retryCount
|
||||
* @returns {EventSource | null}
|
||||
*/
|
||||
function ensureEventSourceOnElement(elt, retryCount) {
|
||||
|
||||
if (elt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// handle extension source creation attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
|
||||
var sseURL = api.getAttributeValue(child, "sse-connect");
|
||||
if (sseURL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureEventSource(child, sseURL, retryCount);
|
||||
});
|
||||
|
||||
// handle legacy sse, remove for HTMX2
|
||||
queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
|
||||
var sseURL = getLegacySSEURL(child);
|
||||
if (sseURL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureEventSource(child, sseURL, retryCount);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function ensureEventSource(elt, url, retryCount) {
|
||||
var source = htmx.createEventSource(url);
|
||||
|
||||
source.onerror = function(err) {
|
||||
|
||||
// Log an error event
|
||||
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
|
||||
|
||||
// If parent no longer exists in the document, then clean up this EventSource
|
||||
if (maybeCloseSSESource(elt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, try to reconnect the EventSource
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
retryCount = retryCount || 0;
|
||||
var timeout = Math.random() * (2 ^ retryCount) * 500;
|
||||
window.setTimeout(function() {
|
||||
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
source.onopen = function(evt) {
|
||||
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
|
||||
}
|
||||
|
||||
api.getInternalData(elt).sseEventSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* maybeCloseSSESource confirms that the parent element still exists.
|
||||
* If not, then any associated SSE source is closed and the function returns true.
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
* @returns boolean
|
||||
*/
|
||||
function maybeCloseSSESource(elt) {
|
||||
if (!api.bodyContains(elt)) {
|
||||
var source = api.getInternalData(elt).sseEventSource;
|
||||
if (source != undefined) {
|
||||
source.close();
|
||||
// source = null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
* @param {string} attributeName
|
||||
*/
|
||||
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||
|
||||
var result = [];
|
||||
|
||||
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||
if (api.hasAttribute(elt, attributeName)) {
|
||||
result.push(elt);
|
||||
}
|
||||
|
||||
// Search all child nodes that match the requested attribute
|
||||
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
|
||||
result.push(node);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} elt
|
||||
* @param {string} content
|
||||
*/
|
||||
function swap(elt, content) {
|
||||
|
||||
api.withExtensions(elt, function(extension) {
|
||||
content = extension.transformResponse(content, null, elt);
|
||||
});
|
||||
|
||||
var swapSpec = api.getSwapSpecification(elt);
|
||||
var target = api.getTarget(elt);
|
||||
var settleInfo = api.makeSettleInfo(elt);
|
||||
|
||||
api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
|
||||
|
||||
settleInfo.elts.forEach(function(elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add(htmx.config.settlingClass);
|
||||
}
|
||||
api.triggerEvent(elt, 'htmx:beforeSettle');
|
||||
});
|
||||
|
||||
// Handle settle tasks (with delay if requested)
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
|
||||
} else {
|
||||
doSettle(settleInfo)();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* doSettle mirrors much of the functionality in htmx that
|
||||
* settles elements after their content has been swapped.
|
||||
* TODO: this should be published by htmx, and not duplicated here
|
||||
* @param {import("../htmx").HtmxSettleInfo} settleInfo
|
||||
* @returns () => void
|
||||
*/
|
||||
function doSettle(settleInfo) {
|
||||
|
||||
return function() {
|
||||
settleInfo.tasks.forEach(function(task) {
|
||||
task.call();
|
||||
});
|
||||
|
||||
settleInfo.elts.forEach(function(elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove(htmx.config.settlingClass);
|
||||
}
|
||||
api.triggerEvent(elt, 'htmx:afterSettle');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hasEventSource(node) {
|
||||
return api.getInternalData(node).sseEventSource != null;
|
||||
}
|
||||
|
||||
})();
|
1
public/js/htmx.min.js
vendored
Normal file
1
public/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -11,6 +11,7 @@ import { Images, removeImage } from "../controllers/images.js";
|
|||
import { Networks, removeNetwork } from "../controllers/networks.js";
|
||||
import { Volumes, removeVolume } from "../controllers/volumes.js";
|
||||
import { Account } from "../controllers/account.js";
|
||||
import { Variables } from "../controllers/variables.js";
|
||||
import { Settings } from "../controllers/settings.js";
|
||||
import { Supporters } from "../controllers/supporters.js";
|
||||
import { Syslogs } from "../controllers/syslogs.js";
|
||||
|
@ -55,6 +56,7 @@ router.get("/users", auth, Users);
|
|||
router.get("/syslogs", auth, Syslogs);
|
||||
|
||||
router.get("/account", Account);
|
||||
router.get("/variables", auth, Variables);
|
||||
router.get("/settings", auth, Settings);
|
||||
router.get("/supporters", Supporters);
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/meters.css" rel="stylesheet"/>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<script src="/js/htmx-sse.js"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
|
|
|
@ -49,11 +49,10 @@
|
|||
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<a href="./QuickConnect.bat" class="btn" download="QuickConnect.bat">
|
||||
<!-- Download SVG icon from https://tabler-icons.io/i/brand-tabler-->
|
||||
<!-- <a href="./QuickConnect.bat" class="btn" download="QuickConnect.bat">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-windows" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M17.8 20l-12 -1.5c-1 -.1 -1.8 -.9 -1.8 -1.9v-9.2c0 -1 .8 -1.8 1.8 -1.9l12 -1.5c1.2 -.1 2.2 .8 2.2 1.9v12.1c0 1.2 -1.1 2.1 -2.2 1.9z"></path> <path d="M12 5l0 14"></path> <path d="M4 12l16 0"></path> </svg>
|
||||
Windows QuickConnect
|
||||
</a>
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
|
|
118
views/variables.html
Normal file
118
views/variables.html
Normal file
|
@ -0,0 +1,118 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Settings</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<%- include('sidebar.html') %>
|
||||
<div class="col d-flex flex-column">
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="mb-2">Settings</h2>
|
||||
<p class="text-muted mb-4">Configure server below</p>
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Full Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">First Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Last Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle">This contact will be shown to others publicly, so choose it carefully.</p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn">Change</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Password</h3>
|
||||
<p class="card-subtitle">You can set a permanent password if you don't want to use temporary login codes.</p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Public profile</h3>
|
||||
<p class="card-subtitle">Making your profile public means that anyone on the Dashkit network will be able to find
|
||||
you.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" >
|
||||
<span class="form-check-label form-check-label-on">You're currently visible</span>
|
||||
<span class="form-check-label form-check-label-off">You're
|
||||
currently invisible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue