piazza 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {piazza-0.2.0/src/piazza.egg-info → piazza-0.2.2}/PKG-INFO +3 -1
- {piazza-0.2.0 → piazza-0.2.2}/README.md +2 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/__init__.py +1 -1
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/admin.html +294 -15
- piazza-0.2.2/src/piazza/admin/auth.py +303 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/handlers.py +48 -4
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/_shared.py +1 -1
- piazza-0.2.2/src/piazza/admin/routes/tokens.py +68 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/server.py +20 -11
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/bus.py +5 -23
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/cli.py +42 -11
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/frontends/http.py +183 -34
- piazza-0.2.2/src/piazza/token_store.py +256 -0
- {piazza-0.2.0 → piazza-0.2.2/src/piazza.egg-info}/PKG-INFO +3 -1
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza.egg-info/SOURCES.txt +4 -1
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_admin.py +76 -24
- piazza-0.2.2/tests/test_token_store.py +156 -0
- piazza-0.2.0/src/piazza/admin/auth.py +0 -85
- {piazza-0.2.0 → piazza-0.2.2}/LICENSE +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/pyproject.toml +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/setup.cfg +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/__init__.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/__init__.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/channels.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/dashboard.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/messages.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/subscriptions.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/routes/ui.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/admin/static.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/backends/__init__.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/backends/memory.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/backends/sqlite.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/client.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/frontends/__init__.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/frontends/base.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/frontends/irc.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/protocols.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/serializers.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/server.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/transport.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/transport_http.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza/types.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza.egg-info/dependency_links.txt +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza.egg-info/entry_points.txt +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza.egg-info/requires.txt +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/src/piazza.egg-info/top_level.txt +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_bus.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_cli.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_client.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_irc_frontend.py +0 -0
- {piazza-0.2.0 → piazza-0.2.2}/tests/test_remote_transport.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: piazza
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Agent messaging hub for multi-agent collaboration.
|
|
5
5
|
Author-email: Oaklight <oaklight@gmx.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -35,6 +35,8 @@ Dynamic: license-file
|
|
|
35
35
|
# Piazza
|
|
36
36
|
|
|
37
37
|
[](https://github.com/Oaklight/piazza/actions/workflows/ci.yml)
|
|
38
|
+
[](https://pypi.org/project/piazza/)
|
|
39
|
+
[](https://github.com/Oaklight/piazza/releases/latest)
|
|
38
40
|
[](https://opensource.org/licenses/MIT)
|
|
39
41
|
|
|
40
42
|
English Version | [中文版](README_zh.md)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Piazza
|
|
2
2
|
|
|
3
3
|
[](https://github.com/Oaklight/piazza/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/piazza/)
|
|
5
|
+
[](https://github.com/Oaklight/piazza/releases/latest)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
7
|
|
|
6
8
|
English Version | [中文版](README_zh.md)
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
/* ============== Reset & Base ============== */
|
|
31
31
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
32
|
+
/* Hide main content until auth check completes to prevent flash of unauthenticated content */
|
|
33
|
+
body.auth-pending .app { display: none !important; }
|
|
32
34
|
body {
|
|
33
35
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
34
36
|
background: var(--bg-primary);
|
|
@@ -426,6 +428,54 @@ tr:hover { background: var(--accent-light); }
|
|
|
426
428
|
.toast-success { background: var(--success); color: #fff; }
|
|
427
429
|
.toast-error { background: var(--error); color: #fff; }
|
|
428
430
|
.toast-info { background: var(--accent); color: #fff; }
|
|
431
|
+
|
|
432
|
+
/* ============== Login Overlay ============== */
|
|
433
|
+
.login-overlay {
|
|
434
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
435
|
+
background: rgba(0,0,0,0.5); z-index: 2000;
|
|
436
|
+
display: flex; align-items: center; justify-content: center;
|
|
437
|
+
}
|
|
438
|
+
.login-overlay.hidden { display: none; }
|
|
439
|
+
.login-card {
|
|
440
|
+
background: var(--bg-secondary); border-radius: var(--radius-lg);
|
|
441
|
+
padding: 32px; width: 360px; box-shadow: var(--shadow-lg);
|
|
442
|
+
}
|
|
443
|
+
.login-card h2 { margin-bottom: 20px; font-size: 1.25rem; }
|
|
444
|
+
.login-card input {
|
|
445
|
+
width: 100%; padding: 10px 14px; border: 1px solid var(--border);
|
|
446
|
+
border-radius: var(--radius-sm); font-size: 0.9rem; margin-bottom: 12px;
|
|
447
|
+
outline: none; background: var(--bg-primary);
|
|
448
|
+
}
|
|
449
|
+
.login-card input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-light); }
|
|
450
|
+
.login-error { color: var(--error); font-size: 0.8rem; margin-bottom: 8px; min-height: 1.2em; }
|
|
451
|
+
|
|
452
|
+
/* ============== Modal ============== */
|
|
453
|
+
.modal-overlay {
|
|
454
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
455
|
+
background: rgba(0,0,0,0.4); z-index: 1500;
|
|
456
|
+
display: flex; align-items: center; justify-content: center;
|
|
457
|
+
}
|
|
458
|
+
.modal-overlay.hidden { display: none; }
|
|
459
|
+
.modal-card {
|
|
460
|
+
background: var(--bg-secondary); border-radius: var(--radius-lg);
|
|
461
|
+
padding: 28px; width: 440px; box-shadow: var(--shadow-lg);
|
|
462
|
+
}
|
|
463
|
+
.modal-card h3 { margin-bottom: 16px; }
|
|
464
|
+
|
|
465
|
+
/* ============== Token Reveal ============== */
|
|
466
|
+
.token-reveal {
|
|
467
|
+
background: #FFF8E1; border: 1px solid #F9A825; border-radius: var(--radius-md);
|
|
468
|
+
padding: 16px; margin: 12px 0;
|
|
469
|
+
}
|
|
470
|
+
.token-reveal-label { font-size: 0.8rem; font-weight: 600; color: #E65100; margin-bottom: 6px; }
|
|
471
|
+
.token-reveal-value {
|
|
472
|
+
font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.85rem;
|
|
473
|
+
word-break: break-all; background: #FFF3E0; padding: 8px; border-radius: 4px;
|
|
474
|
+
margin-bottom: 8px;
|
|
475
|
+
}
|
|
476
|
+
.btn-danger { background: var(--error); color: #fff; }
|
|
477
|
+
.btn-danger:hover { background: #A33636; }
|
|
478
|
+
.btn-sm-action { padding: 3px 8px; font-size: 0.7rem; margin: 0 2px; }
|
|
429
479
|
@keyframes slideIn { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: translateX(0); } }
|
|
430
480
|
@keyframes slideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(40px); } }
|
|
431
481
|
|
|
@@ -440,7 +490,7 @@ tr:hover { background: var(--accent-light); }
|
|
|
440
490
|
}
|
|
441
491
|
</style>
|
|
442
492
|
</head>
|
|
443
|
-
<body>
|
|
493
|
+
<body class="auth-pending">
|
|
444
494
|
<div class="app">
|
|
445
495
|
<!-- Header -->
|
|
446
496
|
<header class="header">
|
|
@@ -462,6 +512,7 @@ tr:hover { background: var(--accent-light); }
|
|
|
462
512
|
</svg>
|
|
463
513
|
Refresh
|
|
464
514
|
</button>
|
|
515
|
+
<button class="btn btn-secondary btn-sm" id="logoutBtn" onclick="doLogout()" style="display:none">Logout</button>
|
|
465
516
|
</header>
|
|
466
517
|
|
|
467
518
|
<!-- Tabs -->
|
|
@@ -470,6 +521,7 @@ tr:hover { background: var(--accent-light); }
|
|
|
470
521
|
<button class="tab-btn" onclick="switchTab('channels')">Channels</button>
|
|
471
522
|
<button class="tab-btn" onclick="switchTab('messages')">Messages</button>
|
|
472
523
|
<button class="tab-btn" onclick="switchTab('subscriptions')">Subscriptions</button>
|
|
524
|
+
<button class="tab-btn" onclick="switchTab('tokens')">Tokens</button>
|
|
473
525
|
</div>
|
|
474
526
|
|
|
475
527
|
<!-- Dashboard Tab -->
|
|
@@ -587,6 +639,52 @@ tr:hover { background: var(--accent-light); }
|
|
|
587
639
|
</table>
|
|
588
640
|
</div>
|
|
589
641
|
</div>
|
|
642
|
+
|
|
643
|
+
<!-- Tokens Tab -->
|
|
644
|
+
<div class="tab-content" id="tab-tokens">
|
|
645
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
646
|
+
<span style="font-size:0.875rem;color:var(--text-secondary)" id="tokenStatus"></span>
|
|
647
|
+
<button class="btn btn-primary btn-sm" onclick="showCreateTokenModal()">+ Create Token</button>
|
|
648
|
+
</div>
|
|
649
|
+
<div id="tokenRevealArea"></div>
|
|
650
|
+
<div class="table-container">
|
|
651
|
+
<table>
|
|
652
|
+
<thead><tr>
|
|
653
|
+
<th>ID</th><th>Agent ID</th><th>Label</th><th>Prefix</th><th>Created</th><th>Last Used</th><th>Actions</th>
|
|
654
|
+
</tr></thead>
|
|
655
|
+
<tbody id="tokenList"></tbody>
|
|
656
|
+
</table>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<!-- Login Overlay -->
|
|
662
|
+
<div class="login-overlay hidden" id="loginOverlay">
|
|
663
|
+
<div class="login-card">
|
|
664
|
+
<h2>🔐 Piazza Admin</h2>
|
|
665
|
+
<div class="login-error" id="loginError"></div>
|
|
666
|
+
<input type="password" id="loginPassword" placeholder="Admin password" onkeydown="if(event.key==='Enter')doLogin()">
|
|
667
|
+
<button class="btn btn-primary" style="width:100%" onclick="doLogin()">Login</button>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<!-- Create Token Modal -->
|
|
672
|
+
<div class="modal-overlay hidden" id="createTokenModal">
|
|
673
|
+
<div class="modal-card">
|
|
674
|
+
<h3>Create Agent Token</h3>
|
|
675
|
+
<div class="form-group" style="margin-bottom:12px">
|
|
676
|
+
<label>Agent ID</label>
|
|
677
|
+
<input id="newTokenAgentId" type="text" placeholder="agent-alice (leave empty for supertoken)">
|
|
678
|
+
</div>
|
|
679
|
+
<div class="form-group" style="margin-bottom:16px">
|
|
680
|
+
<label>Label</label>
|
|
681
|
+
<input id="newTokenLabel" type="text" placeholder="Description">
|
|
682
|
+
</div>
|
|
683
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
684
|
+
<button class="btn btn-secondary" onclick="hideCreateTokenModal()">Cancel</button>
|
|
685
|
+
<button class="btn btn-primary" onclick="submitCreateToken()">Create</button>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
590
688
|
</div>
|
|
591
689
|
|
|
592
690
|
<!-- Toast Container -->
|
|
@@ -597,28 +695,17 @@ tr:hover { background: var(--accent-light); }
|
|
|
597
695
|
class PiazzaAPI {
|
|
598
696
|
constructor() {
|
|
599
697
|
this.baseUrl = window.location.origin;
|
|
600
|
-
this.token = localStorage.getItem('piazza_token') || null;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
getHeaders() {
|
|
604
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
605
|
-
if (this.token) headers['Authorization'] = 'Bearer ' + this.token;
|
|
606
|
-
return headers;
|
|
607
698
|
}
|
|
608
699
|
|
|
609
700
|
async request(path, options = {}) {
|
|
610
701
|
try {
|
|
611
702
|
const resp = await fetch(this.baseUrl + path, {
|
|
612
|
-
|
|
703
|
+
credentials: 'same-origin',
|
|
704
|
+
headers: { 'Content-Type': 'application/json' },
|
|
613
705
|
...options,
|
|
614
706
|
});
|
|
615
707
|
if (resp.status === 401) {
|
|
616
|
-
|
|
617
|
-
if (token) {
|
|
618
|
-
this.token = token;
|
|
619
|
-
localStorage.setItem('piazza_token', token);
|
|
620
|
-
return this.request(path, options);
|
|
621
|
-
}
|
|
708
|
+
showLoginOverlay();
|
|
622
709
|
throw new Error('Authentication required');
|
|
623
710
|
}
|
|
624
711
|
updateStatus(true);
|
|
@@ -631,6 +718,26 @@ class PiazzaAPI {
|
|
|
631
718
|
}
|
|
632
719
|
}
|
|
633
720
|
|
|
721
|
+
async login(password) {
|
|
722
|
+
const resp = await fetch(this.baseUrl + '/api/login', {
|
|
723
|
+
method: 'POST', credentials: 'same-origin',
|
|
724
|
+
headers: { 'Content-Type': 'application/json' },
|
|
725
|
+
body: JSON.stringify({ password }),
|
|
726
|
+
});
|
|
727
|
+
return { ok: resp.ok, data: await resp.json() };
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async logout() {
|
|
731
|
+
return fetch(this.baseUrl + '/api/logout', {
|
|
732
|
+
method: 'POST', credentials: 'same-origin',
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async checkAuth() {
|
|
737
|
+
const resp = await fetch(this.baseUrl + '/api/auth-check', { credentials: 'same-origin' });
|
|
738
|
+
return resp.json();
|
|
739
|
+
}
|
|
740
|
+
|
|
634
741
|
async getStats() { return this.request('/api/stats'); }
|
|
635
742
|
async getThroughput() { return this.request('/api/stats/throughput'); }
|
|
636
743
|
async getChannels() { return this.request('/api/channels'); }
|
|
@@ -646,6 +753,21 @@ class PiazzaAPI {
|
|
|
646
753
|
return this.request('/api/messages', { method: 'POST', body: JSON.stringify(data) });
|
|
647
754
|
}
|
|
648
755
|
async getSubscriptions() { return this.request('/api/subscriptions'); }
|
|
756
|
+
|
|
757
|
+
// Token management
|
|
758
|
+
async listTokens() { return this.request('/api/tokens'); }
|
|
759
|
+
async createToken(agentId, label) {
|
|
760
|
+
return this.request('/api/tokens', {
|
|
761
|
+
method: 'POST',
|
|
762
|
+
body: JSON.stringify({ agent_id: agentId || null, label: label || '' }),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
async deleteToken(id) {
|
|
766
|
+
return this.request('/api/tokens/' + id, { method: 'DELETE' });
|
|
767
|
+
}
|
|
768
|
+
async rotateToken(id) {
|
|
769
|
+
return this.request('/api/tokens/' + id + '/rotate', { method: 'POST' });
|
|
770
|
+
}
|
|
649
771
|
}
|
|
650
772
|
|
|
651
773
|
const api = new PiazzaAPI();
|
|
@@ -702,6 +824,7 @@ function refreshCurrentTab() {
|
|
|
702
824
|
if (document.getElementById('msgAutoRefresh').checked) loadMessages();
|
|
703
825
|
break;
|
|
704
826
|
case 'subscriptions': loadSubscriptions(); break;
|
|
827
|
+
case 'tokens': loadTokens(); break;
|
|
705
828
|
}
|
|
706
829
|
}
|
|
707
830
|
|
|
@@ -1047,8 +1170,164 @@ function formatTime(isoStr) {
|
|
|
1047
1170
|
} catch(e) { return isoStr; }
|
|
1048
1171
|
}
|
|
1049
1172
|
|
|
1173
|
+
/* ============== Login / Logout ============== */
|
|
1174
|
+
function showLoginOverlay() {
|
|
1175
|
+
document.body.classList.add('auth-pending');
|
|
1176
|
+
document.getElementById('loginOverlay').classList.remove('hidden');
|
|
1177
|
+
document.getElementById('loginError').textContent = '';
|
|
1178
|
+
document.getElementById('loginPassword').value = '';
|
|
1179
|
+
document.getElementById('loginPassword').focus();
|
|
1180
|
+
if (refreshInterval) { clearInterval(refreshInterval); refreshInterval = null; }
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function hideLoginOverlay() {
|
|
1184
|
+
document.getElementById('loginOverlay').classList.add('hidden');
|
|
1185
|
+
document.body.classList.remove('auth-pending');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function doLogin() {
|
|
1189
|
+
const pw = document.getElementById('loginPassword').value;
|
|
1190
|
+
if (!pw) return;
|
|
1191
|
+
try {
|
|
1192
|
+
const result = await api.login(pw);
|
|
1193
|
+
if (result.ok) {
|
|
1194
|
+
hideLoginOverlay();
|
|
1195
|
+
document.getElementById('logoutBtn').style.display = '';
|
|
1196
|
+
await loadDashboard();
|
|
1197
|
+
populateChannelSelector();
|
|
1198
|
+
refreshInterval = setInterval(refreshCurrentTab, 3000);
|
|
1199
|
+
} else {
|
|
1200
|
+
document.getElementById('loginError').textContent = result.data.error || 'Invalid password';
|
|
1201
|
+
}
|
|
1202
|
+
} catch (e) {
|
|
1203
|
+
document.getElementById('loginError').textContent = 'Connection error';
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
async function doLogout() {
|
|
1208
|
+
await api.logout();
|
|
1209
|
+
document.getElementById('logoutBtn').style.display = 'none';
|
|
1210
|
+
showLoginOverlay();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* ============== Token Management ============== */
|
|
1214
|
+
async function loadTokens() {
|
|
1215
|
+
try {
|
|
1216
|
+
const data = await api.listTokens();
|
|
1217
|
+
const tbody = document.getElementById('tokenList');
|
|
1218
|
+
const status = document.getElementById('tokenStatus');
|
|
1219
|
+
status.textContent = data.enabled
|
|
1220
|
+
? `${data.tokens.length} token(s) configured`
|
|
1221
|
+
: 'Token auth not configured';
|
|
1222
|
+
if (data.tokens.length === 0) {
|
|
1223
|
+
tbody.innerHTML = '<tr><td colspan="7" style="color:var(--text-secondary);text-align:center;padding:40px">No tokens yet — API is open until the first token is created</td></tr>';
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
tbody.innerHTML = data.tokens.map(t => `
|
|
1227
|
+
<tr>
|
|
1228
|
+
<td style="font-family:monospace;font-size:0.8rem">${esc(t.id)}</td>
|
|
1229
|
+
<td>${t.agent_id ? esc(t.agent_id) : '<em style="color:var(--warning)">supertoken</em>'}</td>
|
|
1230
|
+
<td>${esc(t.label)}</td>
|
|
1231
|
+
<td style="font-family:monospace;font-size:0.8rem">${esc(t.token_prefix)}…</td>
|
|
1232
|
+
<td style="font-size:0.8rem;color:var(--text-secondary)">${formatTime(t.created_at)}</td>
|
|
1233
|
+
<td style="font-size:0.8rem;color:var(--text-secondary)">${t.last_used_at ? formatTime(t.last_used_at) : '-'}</td>
|
|
1234
|
+
<td>
|
|
1235
|
+
<button class="btn btn-secondary btn-sm-action" onclick="rotateToken('${t.id}')">Rotate</button>
|
|
1236
|
+
<button class="btn btn-danger btn-sm-action" onclick="deleteToken('${t.id}')">Delete</button>
|
|
1237
|
+
</td>
|
|
1238
|
+
</tr>
|
|
1239
|
+
`).join('');
|
|
1240
|
+
} catch (e) {
|
|
1241
|
+
console.error('Tokens load error:', e);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function showCreateTokenModal() {
|
|
1246
|
+
document.getElementById('createTokenModal').classList.remove('hidden');
|
|
1247
|
+
document.getElementById('newTokenAgentId').value = '';
|
|
1248
|
+
document.getElementById('newTokenLabel').value = '';
|
|
1249
|
+
document.getElementById('newTokenAgentId').focus();
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function hideCreateTokenModal() {
|
|
1253
|
+
document.getElementById('createTokenModal').classList.add('hidden');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
async function submitCreateToken() {
|
|
1257
|
+
const agentId = document.getElementById('newTokenAgentId').value.trim();
|
|
1258
|
+
const label = document.getElementById('newTokenLabel').value.trim();
|
|
1259
|
+
try {
|
|
1260
|
+
const result = await api.createToken(agentId, label);
|
|
1261
|
+
hideCreateTokenModal();
|
|
1262
|
+
if (result.ok && result.token) {
|
|
1263
|
+
showTokenReveal(result.token.token, 'New token created — copy it now, it won\'t be shown again:');
|
|
1264
|
+
showToast('Token created for ' + (agentId || 'supertoken'), 'success');
|
|
1265
|
+
}
|
|
1266
|
+
loadTokens();
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
showToast('Failed to create token', 'error');
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
async function deleteToken(id) {
|
|
1273
|
+
if (!confirm('Delete this token? Agents using it will be locked out immediately.')) return;
|
|
1274
|
+
try {
|
|
1275
|
+
await api.deleteToken(id);
|
|
1276
|
+
showToast('Token deleted', 'success');
|
|
1277
|
+
loadTokens();
|
|
1278
|
+
} catch (e) {
|
|
1279
|
+
showToast('Failed to delete token', 'error');
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async function rotateToken(id) {
|
|
1284
|
+
if (!confirm('Rotate this token? The old value will stop working immediately.')) return;
|
|
1285
|
+
try {
|
|
1286
|
+
const result = await api.rotateToken(id);
|
|
1287
|
+
if (result.ok && result.token) {
|
|
1288
|
+
showTokenReveal(result.token.token, 'Token rotated — copy the new value:');
|
|
1289
|
+
showToast('Token rotated', 'success');
|
|
1290
|
+
}
|
|
1291
|
+
loadTokens();
|
|
1292
|
+
} catch (e) {
|
|
1293
|
+
showToast('Failed to rotate token', 'error');
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function showTokenReveal(tokenValue, label) {
|
|
1298
|
+
const area = document.getElementById('tokenRevealArea');
|
|
1299
|
+
area.innerHTML = `
|
|
1300
|
+
<div class="token-reveal">
|
|
1301
|
+
<div class="token-reveal-label">⚠️ ${esc(label)}</div>
|
|
1302
|
+
<div class="token-reveal-value">${esc(tokenValue)}</div>
|
|
1303
|
+
<button class="btn btn-primary btn-sm" onclick="copyToken('${esc(tokenValue)}')">📋 Copy to Clipboard</button>
|
|
1304
|
+
<button class="btn btn-secondary btn-sm" onclick="this.parentElement.remove()" style="margin-left:8px">Dismiss</button>
|
|
1305
|
+
</div>
|
|
1306
|
+
`;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function copyToken(token) {
|
|
1310
|
+
navigator.clipboard.writeText(token).then(
|
|
1311
|
+
() => showToast('Token copied to clipboard', 'success'),
|
|
1312
|
+
() => showToast('Failed to copy — select and copy manually', 'error')
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1050
1316
|
/* ============== Init ============== */
|
|
1051
1317
|
async function init() {
|
|
1318
|
+
try {
|
|
1319
|
+
const auth = await api.checkAuth();
|
|
1320
|
+
if (auth.required && !auth.authenticated) {
|
|
1321
|
+
showLoginOverlay();
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (auth.required) {
|
|
1325
|
+
document.getElementById('logoutBtn').style.display = '';
|
|
1326
|
+
}
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
// No auth configured or server unreachable — proceed without login
|
|
1329
|
+
}
|
|
1330
|
+
document.body.classList.remove('auth-pending');
|
|
1052
1331
|
await loadDashboard();
|
|
1053
1332
|
populateChannelSelector();
|
|
1054
1333
|
refreshInterval = setInterval(refreshCurrentTab, 3000);
|