addition of new dashboard

This commit is contained in:
2026-08-20 15:08:32 +00:00
parent 0622018d95
commit bf59249ed7
161 changed files with 13170 additions and 119 deletions

View File

@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<%- include('./include/header.ejs', { title: 'JLINC - MyTerms Agreement' }) %>
<style>
a {
color: #31A9BA !important
}
</style>
<div class="mdc-card" style="background: linear-gradient(333deg, rgb(0, 0, 0) 0%, rgb(79, 55, 139) 100%);">
<%- agreement %>
</div>
<br>
View the <a href="<%- rawUrl %>">raw agreement content</a>.
<%- include('./include/footer.ejs') %>

View File

@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<%- include('./include/header.ejs', { title: 'JLINC - Dashboard' }) %>
<% for (const type in config.appModules) { %>
<% if (config.appModules[type].internal) { continue; } %>
<%-
include('./include/app.ejs', {
app: config.appModules[type],
usage: usage ? usage[type] : null
})
%>
<% } %>
<%-
include('./include/agreements.ejs', {
app: config.appModules['core']
})
%>
<%- include('./include/footer.ejs') %>

View File

@@ -0,0 +1,26 @@
<% const cardStyle=`"background: linear-gradient(333deg, ${app.background.color1} 0%, ${app.background.color2} 100%);
margin-bottom: 30px;"`; const userApp=user.apps.find(ua=> app.type === 'core');
const buttonStyle = `"padding: 8px 10px 8px 10px; border-radius: 16px !important; background-color:
${app.button.color} !important; border: none !important;"`
%>
<div class="mdc-card mdc-theme--dark" style=<%- cardStyle %>>
<div style="display: flex; align-items: center;">
<div style="width: 30px">
<%- app.logo %>
</div>
<h2 style="margin-left: 10px; padding-bottom: 10px;">
Available Agreements
</h2>
</div>
<div style="display: flex; align-items: center;">
<ul style="margin-top: 0px">
<% for (const agreement of agreements) { %>
<li style="padding-bottom: 1em"><a target="_new" class="agreement-link" href="/agreements/<%- agreement.hash %>"><%- agreement.title %></a></li>
<% } %>
</ul>
</div>
</div>

View File

@@ -0,0 +1,163 @@
<% const cardStyle=`"background: linear-gradient(333deg, ${app.background.color1} 0%, ${app.background.color2} 100%);
margin-bottom: 30px;"`; const userApp=user.apps.find(ua=> app.type === ua.type);
const buttonStyle = `"padding: 8px 10px 8px 10px; border-radius: 16px !important; background-color:
${app.button.color} !important; border: none !important;"`
%>
<script>
function copyToClipboard(label, text) {
if (window.clipboardData && window.clipboardData.setData) {
// Internet Explorer specific code path to prevent textarea being shown while dialog is visible.
return clipboardData.setData('Text', text);
} else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
var textarea = document.createElement("textarea");
textarea.textContent = text;
// Prevent scrolling to bottom of page in Microsoft Edge.
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
textarea.select();
const flash = document.createElement('div');
flash.style.position = 'fixed';
flash.style.top = '0';
flash.style.left = '0';
flash.style.width = '100%';
flash.style.backgroundColor = '#000';
flash.style.color = '#ggg';
flash.style.opacity = '0.7';
flash.style.textAlign = 'center';
flash.style.fontSize = '12px';
flash.style.padding = '10px';
try {
const cmd = document.execCommand('copy'); // Security exception may be thrown by some browsers.
flash.innerHTML = `${label} copied to clipboard`;
document.body.appendChild(flash);
setTimeout(function () {
document.body.removeChild(flash);
}, 1500);
return cmd;
} catch (ex) {
flash.style.backgroundColor = '#f00';
flash.innerHTML = 'API key copy failed';
document.body.appendChild(flash);
setTimeout(function () {
document.body.removeChild(flash);
}, 1500);
return false;
} finally {
document.body.removeChild(textarea);
}
}
}
</script>
<div class="mdc-card mdc-theme--dark" style=<%- cardStyle %>>
<div style="display: flex; align-items: center;">
<div style="width: 30px">
<%- app.logo %>
</div>
<h2 style="margin-left: 10px; padding-bottom: 10px;">
<%= app.title %>
</h2>
</div>
<label class="mdc-text-field mdc-text-field--outlined mdc-text-field--focused">
<span class="mdc-notched-outline" style="--mdc-theme-primary: rgba(255, 255, 255, 0.3)">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__trailing"></span>
</span>
<input style="color: #fff; text-overflow: ellipsis;" type="text" id="endpoint-input"
aria-describedby="api-key-helper" class="mdc-text-field__input" disabled type="text"
value="<%= app.endpoint %>">
<i style="color: #fff" class="material-icons mdc-text-field__icon mdc-text-field__icon--trailing"
tabindex="0" role="button" onclick="copyToClipboard('API Endpoint', '<%= app.endpoint %>')">
content_copy
</i>
</label>
<div class="mdc-text-field-helper-line" style="padding-bottom: 20px">
<div style="color: #fff" class="mdc-text-field-helper-text" id="endpoint-helper" aria-hidden="false">API
Endpoint
</div>
</div>
<label class="mdc-text-field mdc-text-field--outlined mdc-text-field--focused">
<span class="mdc-notched-outline" style="--mdc-theme-primary: rgba(255, 255, 255, 0.3)">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__trailing"></span>
</span>
<input style="color: #fff; text-overflow: ellipsis;" type="text" id="api-key-input"
aria-describedby="api-key-helper" class="mdc-text-field__input" disabled type="text"
value="<%= userApp.apiKey %>">
<i style="color: #fff" class="material-icons mdc-text-field__icon mdc-text-field__icon--trailing"
tabindex="0" role="button" onclick="copyToClipboard('API Key', '<%= userApp.apiKey %>')">
content_copy
</i>
</label>
<div class="mdc-text-field-helper-line">
<div style="color: #fff" class="mdc-text-field-helper-text" id="api-key-helper" aria-hidden="false">API Key
</div>
</div>
<div style="display: flex; justify-content: flex-end;">
<button style="width: 140px; --mdc-theme-primary: <%= app.button.color %>"
class="mdc-button mdc-button--raised mdc-button--leading"
onclick="window.location.href='/refresh?app=<%= app.type %>'">
<span class="mdc-button__ripple"></span>
<i class="material-icons mdc-button__icon" aria-hidden="true">refresh</i>
<span class="mdc-button__label">Refresh</span>
</button>
</div>
<% if (usage) { %>
<div style="display: flex; align-items: center;">
<h3 style="margin: 0; display: flex; align-items: center; margin-right: 16px;">Hits this month:</h3>
<span class="mdc-evolution-chip-set" role="grid">
<span class="mdc-evolution-chip-set__chips" role="presentation">
<span class="mdc-evolution-chip" role="row">
<span class="mdc-evolution-chip__cell mdc-evolution-chip__cell--primary" role="gridcell">
<button style=<%- buttonStyle %>
class="mdc-evolution-chip__action mdc-evolution-chip__action--primary" type="button"
tabindex="0">
<span class="mdc-evolution-chip__ripple mdc-evolution-chip__ripple--primary"></span>
<span class="mdc-evolution-chip__text-label">
<%= usage %>
</span>
</button>
</span>
</span>
</span>
</span>
</div>
<% } %>
<% if (userApp.devices?.length> 0) { %>
<h3 style="margin-bottom: 0px">Synced devices</h3>
<span class="mdc-evolution-chip-set" role="grid" style="padding-top: 16px">
<span class="mdc-evolution-chip-set__chips" role="presentation">
<% for (const device of userApp.devices.sort((a, b)=>
a.identifier.localeCompare(b.identifier))) {
%>
<span style="padding-right: 10px" class="mdc-evolution-chip" role="row"
id="device-<%- device.id %>">
<span class="mdc-evolution-chip__cell mdc-evolution-chip__cell--primary"
role="gridcell">
<button style=<%- buttonStyle %> class="mdc-evolution-chip__action
mdc-evolution-chip__action--primary"
type="button" tabindex="0">
<span
class="mdc-evolution-chip__ripple mdc-evolution-chip__ripple--primary"></span>
<span class="mdc-evolution-chip__text-label">
<%= device.identifier %>
</span>
</button>
</span>
</span>
<% } %>
</span>
</span>
<% } %>
</div>

View File

@@ -0,0 +1,6 @@
</div>
</center>
</div>
</body>
</html>

View File

@@ -0,0 +1,125 @@
<head>
<title>
<%= title %>
</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/images/icon.svg" type="image/x-icon">
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500&display=swap" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<style>
:root {
--md-ref-typeface-brand: 'Open Sans', sans-serif;
--md-ref-typeface-plain: system-ui, sans-serif;
}
body {
font-family: var(--md-ref-typeface-plain);
color: #fff;
background-color: rgba(0, 0, 0, 0);
background-image: linear-gradient(135deg, #31A9BA 10%, #0E0618 10%, #231641 90%, #31A9BA 90%);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
div {
font-family: var(--md-ref-typeface-plain);
}
h1,
h2,
h3 {
font-family: var(--md-ref-typeface-brand);
}
h1 {
font-weight: 400;
}
h2 {
font-weight: 500;
}
h3 {
font-weight: 500;
}
.mdc-card {
padding-bottom: 16px;
padding-left: 16px;
padding-right: 16px;
background-color: rgb(68, 62, 77);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.mdc-button {
--mdc-theme-primary: rgb(255, 255, 255, .85);
--mdc-theme-on-primary: rgb(68, 62, 77);
/* @include button.ink-color(#84565E); */
}
.agreement-link {
font-family: var(--md-ref-typeface-plain);
color: white;
}
</style>
</head>
<body>
<div class="profile-icon-container" style="position: absolute; top: 16px; right: 16px;">
<% if (typeof user !=='undefined' && user) { %>
<div style="position: relative;">
<button id="profileButton" class="profile-icon"
style="border: none; border-radius: 50%; width: 48px; height: 48px; display: flex; justify-content: center; align-items: center; background-color: #31A9BA; color: #fff; cursor: pointer;">
<span style="font-size: 30px">
<%= user.username.charAt(0).toUpperCase() %>
</span>
</button>
<div id="profileMenu"
style="display: none; position: absolute; top: 60px; right: 0; background-color: #4F378B; box-shadow: 0 2px 8px rgba(0,0,0,0.15); border-radius: 8px; padding: 10px; min-width: 160px; z-index: 100;">
<div style="padding: 8px; font-weight: bold;">
<span style="display: block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<%= user.username %>
</span>
</div>
<form method="POST" action="/logout">
<button type="submit"
style="width: 100%; padding: 8px; background-color: #31A9BA; color: white; border: none; border-radius: 4px; cursor: pointer;">Logout</button>
</form>
</div>
</div>
<script>
const profileButton = document.getElementById('profileButton');
const profileMenu = document.getElementById('profileMenu');
profileButton.addEventListener('click', () => {
profileMenu.style.display = profileMenu.style.display === 'block' ? 'none' : 'block';
});
// Optional: Hide menu if clicking outside
document.addEventListener('click', (event) => {
if (!profileButton.contains(event.target) && !profileMenu.contains(event.target)) {
profileMenu.style.display = 'none';
}
});
</script>
<% } %>
</div>
<div style="width: 100%; overflow-y: auto; max-height: 100%; word-wrap: break-word;">
<center>
<div style="width: 90%; max-width: 600px; text-align: left">
<div
style="padding-top: 20px; display: flex; align-items: center; justify-content: center; gap: 0px; text-align: center; transform: translateX(-0px);">
<div style="width: 200px; padding-bottom: 26px">
<%- include('./logo-white.svg') %>
</div>
</div>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 362.08 100.47"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#31a9ba;}</style></defs><title>JLINC Logo H White</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M157.32,29.36h8.35V57.2q0,13.9-13.92,13.91H137.84q-14.22,0-14-13.91l0-1.4h8.35v.25q0,6.72,6.68,6.71h11.66q6.7,0,6.71-6.73Z"/><path class="cls-1" d="M223.12,62.76v8.35H181.37V29.36h8.35v33.4Z"/><path class="cls-1" d="M247.18,71.11h-8.35V29.36h8.35Z"/><path class="cls-1" d="M271.23,42V71.11h-8.35V29.36h8.51l24.89,29.09V29.36h8.35V71.11h-8.49Z"/><path class="cls-1" d="M362.08,62.76v8.35H334.25q-13.92,0-13.92-13.91V43.28q0-13.92,13.92-13.92h27.83v8.35H335.39q-6.7,0-6.71,6.68V56.05q0,6.72,6.74,6.71Z"/><path class="cls-2" d="M8.52,41.22l9,9L36.46,69.16l7.76-7.76L25.3,42.48l-9.42-9.42q-4.5-4.52-5.14-9.26c-.48-3.11,1-6.36,4.35-9.73Q19.22,9.95,23.64,10t9.26,4.83l4,4,7.2-7.21L38.52,6a20.93,20.93,0,0,0-7.91-4.9A17.1,17.1,0,0,0,20,.61Q14,2,8.05,8a28.56,28.56,0,0,0-6.33,9.5,18.2,18.2,0,0,0-.79,11.4Q2.34,35,8.52,41.22Z"/><path class="cls-2" d="M61.4,56.25,42.48,75.17l-9.42,9.42q-4.52,4.51-9.26,5.14t-9.73-4.35Q9.95,81.27,10,76.83t4.83-9.26l4-3.95-7.21-7.21L6,62a21,21,0,0,0-4.9,7.92A17.09,17.09,0,0,0,.61,80.48q1.43,6,7.36,12a28.87,28.87,0,0,0,9.5,6.33,18.2,18.2,0,0,0,11.4.79Q35,98.13,41.22,92l9-9L69.16,64Z"/><path class="cls-2" d="M92,59.26l-9-9L64,31.31l-7.76,7.76L75.17,58l9.42,9.42q4.51,4.52,5.14,9.26t-4.35,9.74c-2.74,2.74-5.59,4.12-8.55,4.11s-6-1.55-9.26-4.83l-3.95-4-7.21,7.2L62,94.48a21.11,21.11,0,0,0,7.92,4.91,17.06,17.06,0,0,0,10.6.47q6-1.42,12-7.36A28.87,28.87,0,0,0,98.76,83a18.16,18.16,0,0,0,.79-11.39Q98.13,65.43,92,59.26Z"/><path class="cls-2" d="M39.07,44.22,58,25.3l9.42-9.42q4.52-4.5,9.26-5.14c3.12-.48,6.36,1,9.74,4.35,2.74,2.75,4.11,5.59,4.11,8.55s-1.55,6-4.83,9.26l-4,4,7.2,7.2,5.54-5.54a21.07,21.07,0,0,0,4.91-7.91A17.17,17.17,0,0,0,99.86,20Q98.44,14,92.5,8.05A28.56,28.56,0,0,0,83,1.72,18.19,18.19,0,0,0,71.6.93Q65.44,2.34,59.26,8.52l-9,9L31.31,36.46Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<%- include('./include/header.ejs', { title: 'JLINC - Login' }) %>
<div class="mdc-card" style="background: linear-gradient(333deg, rgb(0, 0, 0) 0%, rgb(79, 55, 139) 100%);">
<h3>Login with:</h3>
<% for (const type in config.authModules) { %>
<% if (type !== 'single') { %>
<div style="width: 100%; padding-bottom: 16px">
<button style="width: 100%" class="mdc-button mdc-button--raised mdc-button--leading" onclick="window.location.href='/login/<%= type %>'">
<span class="mdc-button__ripple"></span>
<i class="material-icons mdc-button__icon" aria-hidden="true"><%= config.authModules[type].icon %></i>
<span class="mdc-button__label"><%= config.authModules[type].title %></span>
</button>
</div>
<% } %>
<% } %>
</div>
<%- include('./include/footer.ejs') %>