feat(gateway-ui): rewrite all page renderers with Tailwind classes
Convert dashboard, chat, sessions, usage, and settings pages from legacy CSS to Tailwind utility classes. Responsive grid layouts, mobile-friendly touch targets, zinc/blue color palette. All element IDs and event bindings preserved for functional compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,21 +53,22 @@ async function loadSessionList() {
|
||||
const sessions = result.sessions ?? [];
|
||||
|
||||
if (sessions.length === 0) {
|
||||
listContainer.innerHTML = '<div class="empty-state">No sessions found</div>';
|
||||
listContainer.innerHTML = '<div class="text-center py-12 px-6 text-zinc-500 text-sm">No sessions found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Session ID</th>
|
||||
<th>Frontend</th>
|
||||
<th>Messages</th>
|
||||
<th>Model</th>
|
||||
<th>Queue</th>
|
||||
<th>Last Activity</th>
|
||||
<th>Actions</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Session ID</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Frontend</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Messages</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Model</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Queue</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Last Activity</th>
|
||||
<th class="text-left px-3 py-2 text-xs font-medium text-zinc-400 uppercase tracking-wide border-b border-zinc-800">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -75,22 +76,24 @@ async function loadSessionList() {
|
||||
|
||||
for (const s of sessions) {
|
||||
html += `
|
||||
<tr>
|
||||
<td><a href="#" class="session-view-link" data-id="${escapeHtml(s.id)}">${escapeHtml(s.id)}</a></td>
|
||||
<td>${escapeHtml(s.frontend ?? (String(s.id).split(':')[0] || 'unknown'))}</td>
|
||||
<td>${s.messageCount ?? 0}</td>
|
||||
<td>${escapeHtml(s.config?.modelTier ?? 'default')}</td>
|
||||
<td>${escapeHtml(formatQueue(s.config))}</td>
|
||||
<td>${escapeHtml(formatTime(s.lastMessageAt))}</td>
|
||||
<td class="session-actions">
|
||||
<button class="btn btn-secondary session-view-btn" data-id="${escapeHtml(s.id)}">View</button>
|
||||
<button class="btn btn-danger session-delete-btn" data-id="${escapeHtml(s.id)}">Delete</button>
|
||||
<tr class="hover:bg-zinc-800/50">
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50"><a href="#" class="text-blue-500 hover:underline session-view-link" data-id="${escapeHtml(s.id)}">${escapeHtml(s.id)}</a></td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">${escapeHtml(s.frontend ?? (String(s.id).split(':')[0] || 'unknown'))}</td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">${s.messageCount ?? 0}</td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">${escapeHtml(s.config?.modelTier ?? 'default')}</td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">${escapeHtml(formatQueue(s.config))}</td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">${escapeHtml(formatTime(s.lastMessageAt))}</td>
|
||||
<td class="px-3 py-2 text-zinc-50 border-b border-zinc-800/50">
|
||||
<div class="flex gap-1.5">
|
||||
<button class="px-2.5 py-1 text-xs font-medium rounded-md border border-zinc-700 bg-zinc-800 text-zinc-200 hover:bg-zinc-700 transition-colors session-view-btn" data-id="${escapeHtml(s.id)}">View</button>
|
||||
<button class="px-2.5 py-1 text-xs font-medium rounded-md bg-red-500/15 text-red-500 border border-red-500/30 hover:bg-red-500 hover:text-white transition-colors session-delete-btn" data-id="${escapeHtml(s.id)}">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</tbody></table>';
|
||||
html += '</tbody></table></div>';
|
||||
listContainer.innerHTML = html;
|
||||
|
||||
// Bind view buttons
|
||||
@@ -108,7 +111,7 @@ async function loadSessionList() {
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
listContainer.innerHTML = `<div class="empty-state">Failed to load sessions: ${err.message}</div>`;
|
||||
listContainer.innerHTML = `<div class="text-center py-12 px-6 text-zinc-500 text-sm">Failed to load sessions: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,35 +119,42 @@ async function viewSession(sessionId) {
|
||||
const detailContainer = _el.querySelector('#session-detail');
|
||||
if (!detailContainer) {return;}
|
||||
|
||||
detailContainer.innerHTML = '<div class="empty-state"><span class="spinner"></span> Loading...</div>';
|
||||
detailContainer.innerHTML = '<div class="text-center py-12 px-6 text-zinc-500 text-sm"><span class="spinner"></span> Loading...</div>';
|
||||
|
||||
try {
|
||||
const result = await _client.call('sessions.history', { sessionId });
|
||||
const messages = result.messages ?? [];
|
||||
|
||||
const roleClasses = {
|
||||
user: 'rounded-lg px-3 py-2 text-sm bg-blue-500/15 border border-blue-500/25 text-zinc-50',
|
||||
assistant: 'rounded-lg px-3 py-2 text-sm bg-zinc-900 border border-zinc-800 text-zinc-50',
|
||||
system: 'rounded-lg px-3 py-2 text-sm bg-zinc-800 text-zinc-400 border border-zinc-700',
|
||||
};
|
||||
|
||||
let html = `
|
||||
<div class="session-detail">
|
||||
<div class="session-detail-header">
|
||||
<h2 class="section-title">${escapeHtml(sessionId)}</h2>
|
||||
<span class="text-muted text-sm">${messages.length} messages</span>
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-zinc-50">${escapeHtml(sessionId)}</h2>
|
||||
<span class="text-sm text-zinc-500">${messages.length} messages</span>
|
||||
</div>
|
||||
<div class="message-history">
|
||||
<div class="flex flex-col gap-3 max-h-[60vh] overflow-y-auto p-3 bg-zinc-900 border border-zinc-800 rounded-lg">
|
||||
`;
|
||||
|
||||
if (messages.length === 0) {
|
||||
html += '<div class="empty-state">No messages in this session</div>';
|
||||
html += '<div class="text-center py-12 px-6 text-zinc-500 text-sm">No messages in this session</div>';
|
||||
} else {
|
||||
for (const msg of messages) {
|
||||
const role = msg.role ?? 'system';
|
||||
const content = msg.content ?? msg.text ?? '';
|
||||
html += `<div class="message ${escapeHtml(role)}">${escapeHtml(content)}</div>`;
|
||||
const cls = roleClasses[role] ?? roleClasses.system;
|
||||
html += `<div class="${cls}">${escapeHtml(content)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
detailContainer.innerHTML = html;
|
||||
} catch (err) {
|
||||
detailContainer.innerHTML = `<div class="empty-state">Failed to load session: ${err.message}</div>`;
|
||||
detailContainer.innerHTML = `<div class="text-center py-12 px-6 text-zinc-500 text-sm">Failed to load session: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +177,10 @@ export const SessionsPage = {
|
||||
_el = el;
|
||||
|
||||
el.innerHTML = `
|
||||
<h1 class="page-title">Sessions</h1>
|
||||
<div class="status-row" style="margin-bottom: 0.75rem; gap: 0.75rem; flex-wrap: wrap;">
|
||||
<label class="text-sm text-muted">Frontend
|
||||
<select id="sessions-frontend-filter" style="margin-left: 0.35rem;">
|
||||
<h1 class="text-2xl font-semibold text-zinc-50 mb-6">Sessions</h1>
|
||||
<div class="flex items-center gap-3 mb-4 flex-wrap">
|
||||
<label class="text-sm text-zinc-400 flex items-center gap-1.5">Frontend
|
||||
<select id="sessions-frontend-filter" class="bg-zinc-900 text-zinc-50 border border-zinc-800 rounded-lg px-3 py-1.5 text-sm outline-none focus:border-blue-500">
|
||||
<option value="">All</option>
|
||||
<option value="ws">ws</option>
|
||||
<option value="tui">tui</option>
|
||||
@@ -180,11 +190,11 @@ export const SessionsPage = {
|
||||
<option value="mattermost">mattermost</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="text-sm text-muted">
|
||||
<label class="text-sm text-zinc-400 flex items-center gap-2 cursor-pointer">
|
||||
<input id="sessions-include-inactive" type="checkbox" checked />
|
||||
Include inactive/persisted
|
||||
</label>
|
||||
<button id="sessions-refresh-btn" class="btn btn-secondary">Refresh</button>
|
||||
<button id="sessions-refresh-btn" class="px-3 py-1.5 text-sm font-medium rounded-md border border-zinc-700 bg-zinc-800 text-zinc-200 hover:bg-zinc-700 transition-colors">Refresh</button>
|
||||
</div>
|
||||
<div id="sessions-list"></div>
|
||||
<div id="session-detail"></div>
|
||||
|
||||
Reference in New Issue
Block a user