Skip to content

Commit 55e4ae5

Browse files
committed
Add server dashboard with system info, settings, and action buttons.
1 parent 6bc57ed commit 55e4ae5

13 files changed

Lines changed: 592 additions & 90 deletions

File tree

Public/app/css/main.css

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,97 @@ input[type="text"]:disabled, textarea:disabled {
12771277
background: hsl(var(--muted));
12781278
}
12791279

1280+
/* Dashboard Panel */
1281+
.settings-header-buttons {
1282+
display: flex;
1283+
align-items: center;
1284+
gap: 0.5rem;
1285+
}
1286+
1287+
.dashboard-info-row {
1288+
display: flex;
1289+
justify-content: space-between;
1290+
align-items: center;
1291+
padding: 0.4rem 0;
1292+
}
1293+
1294+
.dashboard-info-label {
1295+
font-size: 0.875rem;
1296+
color: hsl(var(--muted-foreground));
1297+
}
1298+
1299+
.dashboard-info-value {
1300+
font-size: 0.875rem;
1301+
color: hsl(var(--foreground));
1302+
font-weight: 500;
1303+
}
1304+
1305+
.dashboard-loading,
1306+
.dashboard-error {
1307+
font-size: 0.875rem;
1308+
color: hsl(var(--muted-foreground));
1309+
}
1310+
1311+
.dashboard-actions {
1312+
display: flex;
1313+
flex-direction: column;
1314+
gap: 0.75rem;
1315+
}
1316+
1317+
.dashboard-button {
1318+
width: 100%;
1319+
padding: 0.625rem;
1320+
font-size: 0.875rem;
1321+
font-weight: 500;
1322+
border: 1px solid hsl(var(--border));
1323+
border-radius: 0.5rem;
1324+
background: hsl(var(--muted));
1325+
color: hsl(var(--foreground));
1326+
cursor: pointer;
1327+
transition: background 0.15s ease;
1328+
}
1329+
1330+
.dashboard-button:hover {
1331+
background: hsl(var(--accent));
1332+
}
1333+
1334+
.dashboard-button:disabled {
1335+
opacity: 0.5;
1336+
cursor: not-allowed;
1337+
}
1338+
1339+
.dashboard-button-warning {
1340+
background: hsl(0 60% 50% / 0.15);
1341+
border-color: hsl(0 60% 50% / 0.3);
1342+
color: hsl(0 60% 60%);
1343+
}
1344+
1345+
.dashboard-button-warning:hover {
1346+
background: hsl(0 60% 50% / 0.25);
1347+
}
1348+
1349+
.dashboard-log {
1350+
margin: 0;
1351+
margin-top: 0.75rem;
1352+
padding: 0.75rem;
1353+
font-size: 0.75rem;
1354+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
1355+
color: hsl(var(--muted-foreground));
1356+
background: hsl(var(--muted));
1357+
border: 1px solid hsl(var(--border));
1358+
border-radius: 0.5rem;
1359+
white-space: pre-wrap;
1360+
word-break: break-word;
1361+
max-height: 12rem;
1362+
overflow-y: auto;
1363+
}
1364+
1365+
.dashboard-log-error {
1366+
color: hsl(0 60% 60%);
1367+
background: hsl(0 60% 50% / 0.08);
1368+
border-color: hsl(0 60% 50% / 0.2);
1369+
}
1370+
12801371
/* Group Chat Modal */
12811372
.group-chat-modal {
12821373
position: fixed;

Public/app/html/main.leaf

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@
5858

5959
<!-- Bottom Bar: Settings (left) + New Chat (right) -->
6060
<div class="new-chat-button-container">
61-
<button class="inline-button" id="settingsButton" onclick="openSettings()" title="Settings">
62-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings">
63-
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
64-
<circle cx="12" cy="12" r="3"></circle>
65-
</svg>
66-
</button>
61+
<button class="inline-button settings-icon-button" id="settingsButton" onclick="openSettings()" title="Settings"></button>
6762
<button class="inline-button" id="newChatButton" onclick="showNewChatMenu(event)" title="New chat">
6863
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6964
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
@@ -266,7 +261,10 @@
266261
<div class="settings-content">
267262
<div class="settings-header">
268263
<h1 class="text-2xl font-bold text-sidebar-foreground">Settings</h1>
269-
<button class="inline-button close-icon-button" onclick="closeSettings()"></button>
264+
<div class="settings-header-buttons">
265+
<button class="inline-button dashboard-icon-button" id="dashboardButton" style="display:none" title="Server Dashboard" onclick="openDashboard()"></button>
266+
<button class="inline-button close-icon-button" onclick="closeSettings()"></button>
267+
</div>
270268
</div>
271269
<div class="settings-body">
272270
<div class="settings-group">
@@ -302,11 +300,45 @@
302300
</select>
303301
</div>
304302
</div>
305-
<div class="settings-divider" id="serverSettingsDivider" style="display:none"></div>
306-
<div class="settings-group" id="serverSettingsGroup" style="display:none">
307-
<h3 class="settings-group-title">Server</h3>
308-
<div id="serverSettingsList">
309-
<!-- Populated by settings.js -->
303+
</div>
304+
</div>
305+
</div>
306+
307+
<!-- Dashboard Modal -->
308+
<div class="settings-modal" id="dashboardModal" onclick="handleDashboardModalClick(event)">
309+
<div class="settings-content">
310+
<div class="settings-header">
311+
<h1 class="text-2xl font-bold text-sidebar-foreground">Dashboard</h1>
312+
<button class="inline-button close-icon-button" onclick="closeDashboard()"></button>
313+
</div>
314+
<div class="settings-body">
315+
<div class="settings-group">
316+
<h3 class="settings-group-title">Settings</h3>
317+
<div id="dashboardSettingsList">
318+
<!-- Populated by dashboard.js -->
319+
</div>
320+
</div>
321+
<div class="settings-divider"></div>
322+
<div class="settings-group">
323+
<h3 class="settings-group-title">Actions</h3>
324+
<div class="dashboard-actions">
325+
<button class="dashboard-button" id="dashboardRefreshButton" title="Pull latest changes without rebuilding or restarting the server" onclick="dashboardRefresh()">Refresh</button>
326+
<button class="dashboard-button dashboard-button-warning" id="dashboardUpdateButton" title="Pull latest code from remote, rebuild and restart the server" onclick="dashboardUpdate()">Update</button>
327+
</div>
328+
<pre class="dashboard-log" id="dashboardLog" style="display:none"></pre>
329+
</div>
330+
<div class="settings-divider"></div>
331+
<div class="settings-group">
332+
<h3 class="settings-group-title">Product Info</h3>
333+
<div id="dashboardProductInfo" class="dashboard-info">
334+
<p class="dashboard-loading">Loading...</p>
335+
</div>
336+
</div>
337+
<div class="settings-divider"></div>
338+
<div class="settings-group">
339+
<h3 class="settings-group-title">System Info</h3>
340+
<div id="dashboardInfo" class="dashboard-info">
341+
<p class="dashboard-loading">Loading...</p>
310342
</div>
311343
</div>
312344
</div>
@@ -320,6 +352,7 @@
320352
<script src="/app/js/webpush.js?v=23"></script>
321353
<script src="/app/js/svg-icons.js?v=23"></script>
322354
<script src="/app/js/settings.js?v=23"></script>
355+
<script src="/app/js/dashboard.js?v=23"></script>
323356
<script src="/app/js/main.js?v=23"></script>
324357
<script src="/app/js/chat.js?v=23"></script>
325358
<script src="/app/js/ws.js?v=23"></script>

Public/app/js/api.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,49 @@ async function apiSaveServerSetting(name, value) {
241241
return await handleResponse(response);
242242
}
243243

244+
async function apiDashboardInfo() {
245+
const accessToken = getAccessToken();
246+
if (!accessToken) throw new Error('No access token available');
247+
const response = await fetch('/dashboard/api/info', {
248+
headers: { 'Authorization': `Bearer ${accessToken}` }
249+
});
250+
return await handleResponse(response);
251+
}
252+
253+
async function apiDashboardRefresh() {
254+
const accessToken = getAccessToken();
255+
if (!accessToken) throw new Error('No access token available');
256+
const response = await fetch('/dashboard/api/refresh', {
257+
method: 'POST',
258+
headers: { 'Authorization': `Bearer ${accessToken}` }
259+
});
260+
return await handleResponse(response);
261+
}
262+
263+
async function apiDashboardUpdate() {
264+
const accessToken = getAccessToken();
265+
if (!accessToken) throw new Error('No access token available');
266+
const response = await fetch('/dashboard/api/update', {
267+
method: 'POST',
268+
headers: { 'Authorization': `Bearer ${accessToken}` }
269+
});
270+
return await handleResponse(response);
271+
}
272+
273+
async function apiDashboardUpdateLog() {
274+
const accessToken = getAccessToken();
275+
if (!accessToken) throw new Error('No access token available');
276+
const response = await fetch('/dashboard/api/log', {
277+
headers: { 'Authorization': `Bearer ${accessToken}` }
278+
});
279+
return await handleResponse(response);
280+
}
281+
282+
async function apiInfo() {
283+
const response = await fetch('/api/info');
284+
return await handleResponse(response);
285+
}
286+
244287
async function apiGetChats(full = true) {
245288
const accessToken = getAccessToken();
246289
if (!accessToken) {

0 commit comments

Comments
 (0)