feat: add admin REST API layer for frontend management panel

Implement JWT-protected /admin/* routes on the relay server:
- Auth: login, logout, me, setup/status, setup/init (first-time config)
- Dashboard: stats overview (connections, devices, cookies, domains)
- Cookies: paginated list with domain/search filter, detail, delete, batch delete
- Devices: list with online status, revoke
- Settings: get/update (sync interval, max devices, theme)

Uses scrypt for password hashing and jsonwebtoken for JWT.
Adds listAll/revoke to DeviceRegistry, getAll/getById/deleteById to CookieBlobStore,
disconnect to ConnectionManager. Updates frontend to use /admin/* endpoints.

All 38 existing tests pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
徐枫
2026-03-17 20:28:56 +08:00
parent f4144c96f1
commit a320f7ad97
14 changed files with 733 additions and 19 deletions

View File

@@ -80,6 +80,38 @@ export class CookieBlobStore {
return result;
}
/** Get all stored blobs across all devices. */
getAll(): EncryptedCookieBlob[] {
const result: EncryptedCookieBlob[] = [];
for (const deviceMap of this.store.values()) {
result.push(...deviceMap.values());
}
return result;
}
/** Get a single blob by its ID. */
getById(id: string): EncryptedCookieBlob | null {
for (const deviceMap of this.store.values()) {
for (const blob of deviceMap.values()) {
if (blob.id === id) return blob;
}
}
return null;
}
/** Delete a blob by its ID. Returns true if found and deleted. */
deleteById(id: string): boolean {
for (const deviceMap of this.store.values()) {
for (const [key, blob] of deviceMap) {
if (blob.id === id) {
deviceMap.delete(key);
return true;
}
}
}
return false;
}
/** Get all blobs updated after a given timestamp (for polling). */
getUpdatedSince(deviceIds: string[], since: string): EncryptedCookieBlob[] {
const result: EncryptedCookieBlob[] = [];