Add admin invites and user workspace management tools.

Implement invite-token registration with optional email delivery, add admin UI actions for creating invites and opening user workspaces, and support superuser workspace override while preserving per-user code isolation with shared read-only lib.

Made-with: Cursor
This commit is contained in:
2026-05-01 21:13:13 +12:00
parent e4c811f51d
commit 7d682cce8d
15 changed files with 683 additions and 71 deletions

View File

@@ -42,16 +42,22 @@ class TextEditor {
this.ledPanelDismissed = false;
this.lastLedFrame = null;
this.ledPanelWindow = null;
this.workspaceUserId = null;
this.init();
}
init() {
try {
const fromQuery = new URLSearchParams(window.location.search).get('api_key');
const params = new URLSearchParams(window.location.search);
const fromQuery = params.get('api_key');
const workspaceUserId = params.get('workspace_user_id');
if (fromQuery) {
sessionStorage.setItem('python-editor.api_key', fromQuery);
}
if (workspaceUserId && /^\d+$/.test(workspaceUserId)) {
this.workspaceUserId = workspaceUserId;
}
} catch (_error) {
// Ignore query / storage failures.
}
@@ -61,10 +67,22 @@ class TextEditor {
this.setupDevAutoReload();
this.updateRunButtonState();
this.setLspStatus('LSP: n/a', 'Open a Python file for diagnostics');
this.updateWorkspaceBanner();
this.prewarmPyWorker();
this.loadInitialDirectoryState().then(() => this.restoreSessionTabs());
}
updateWorkspaceBanner() {
const badge = document.getElementById('workspace-badge');
if (!badge) return;
if (this.workspaceUserId) {
badge.textContent = `Workspace: user ${this.workspaceUserId}`;
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
}
setupDevAutoReload() {
const isLocalhost =
window.location.hostname === 'localhost' ||
@@ -105,7 +123,19 @@ class TextEditor {
}
next.headers = headers;
next.credentials = 'include';
return fetch(url, next);
let finalUrl = url;
if (this.workspaceUserId && typeof url === 'string' && url.startsWith('/api/')) {
try {
const parsed = new URL(url, window.location.origin);
if (!parsed.searchParams.has('workspace_user_id')) {
parsed.searchParams.set('workspace_user_id', this.workspaceUserId);
}
finalUrl = parsed.pathname + parsed.search;
} catch (_error) {
// ignore URL parse failure and use original
}
}
return fetch(finalUrl, next);
}
disposePyWorker() {