collab-runtime 0.2.9__tar.gz → 0.3.1__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.
- {collab_runtime-0.2.9/collab_runtime.egg-info → collab_runtime-0.3.1}/PKG-INFO +4 -4
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/README.md +36 -16
- collab_runtime-0.3.1/collab/__init__.py +24 -0
- collab_runtime-0.3.1/collab/__main__.py +8 -0
- {collab_runtime-0.2.9/src → collab_runtime-0.3.1/collab}/dashboard/index.html +138 -98
- collab_runtime-0.3.1/collab/dashboard_server.py +242 -0
- collab_runtime-0.3.1/collab/errors.py +86 -0
- {collab_runtime-0.2.9/src → collab_runtime-0.3.1/collab}/live_locks_watcher.py +44 -190
- {collab_runtime-0.2.9/src → collab_runtime-0.3.1/collab}/lock_client.py +376 -542
- {collab_runtime-0.2.9/src → collab_runtime-0.3.1/collab}/logging_config.py +1 -1
- {collab_runtime-0.2.9/src → collab_runtime-0.3.1/collab}/main.py +13 -1
- collab_runtime-0.3.1/collab/platform_probe.py +305 -0
- collab_runtime-0.3.1/collab/safe_subprocess.py +313 -0
- collab_runtime-0.3.1/collab/subprocess_bridge.py +26 -0
- {collab_runtime-0.2.9 → collab_runtime-0.3.1/collab_runtime.egg-info}/PKG-INFO +4 -4
- collab_runtime-0.3.1/collab_runtime.egg-info/SOURCES.txt +22 -0
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/collab_runtime.egg-info/entry_points.txt +1 -1
- collab_runtime-0.3.1/collab_runtime.egg-info/top_level.txt +1 -0
- collab_runtime-0.2.9/README_pypi.md → collab_runtime-0.3.1/docs/pypi/README.md +3 -3
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/pyproject.toml +9 -12
- collab_runtime-0.2.9/collab/__init__.py +0 -77
- collab_runtime-0.2.9/collab/__main__.py +0 -11
- collab_runtime-0.2.9/collab_runtime.egg-info/SOURCES.txt +0 -86
- collab_runtime-0.2.9/collab_runtime.egg-info/top_level.txt +0 -10
- collab_runtime-0.2.9/scripts/cleanup.py +0 -395
- collab_runtime-0.2.9/scripts/collab_git_hook.py +0 -190
- collab_runtime-0.2.9/scripts/format_code.py +0 -594
- collab_runtime-0.2.9/scripts/generate_tests.py +0 -560
- collab_runtime-0.2.9/scripts/validate_code.py +0 -1397
- collab_runtime-0.2.9/src/__init__.py +0 -4
- collab_runtime-0.2.9/tests/backend/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/functional/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/functional/test_package_imports.py +0 -43
- collab_runtime-0.2.9/tests/backend/integration/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/integration/test_cli_contract_parity.py +0 -220
- collab_runtime-0.2.9/tests/backend/performance/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/reliability/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/security/__init__.py +0 -0
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/__init__.py +0 -5
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/_helpers.py +0 -123
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/conftest.py +0 -18
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_dashboard.py +0 -188
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_developer.py +0 -56
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_graceful_shutdown.py +0 -459
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_main.py +0 -1925
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_module.py +0 -187
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_multi_session.py +0 -320
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_notify.py +0 -67
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_parsing.py +0 -155
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_process_helpers.py +0 -684
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_processing.py +0 -173
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_prompt_abort.py +0 -71
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_reconcile.py +0 -516
- collab_runtime-0.2.9/tests/backend/unit/live_locks_watcher/test_live_locks_watcher_scan.py +0 -296
- collab_runtime-0.2.9/tests/backend/unit/lock_client/__init__.py +0 -1
- collab_runtime-0.2.9/tests/backend/unit/lock_client/_helpers.py +0 -132
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_acquire.py +0 -214
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_active.py +0 -104
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_api.py +0 -63
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_cli.py +0 -682
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_daemon.py +0 -3730
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_dashboard.py +0 -438
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_discover.py +0 -241
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_force_release.py +0 -354
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_helper_branches.py +0 -1890
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_history.py +0 -301
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_isolation.py +0 -316
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_pid.py +0 -75
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_reconcile.py +0 -464
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_release.py +0 -77
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_shutdown.py +0 -1110
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_utils.py +0 -474
- collab_runtime-0.2.9/tests/backend/unit/lock_client/test_lock_client_watch.py +0 -866
- collab_runtime-0.2.9/tests/backend/unit/scripts/__init__.py +0 -1
- collab_runtime-0.2.9/tests/backend/unit/scripts/_helpers.py +0 -42
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_cleanup.py +0 -285
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_collab_git_hook.py +0 -280
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_collab_git_hook_ported.py +0 -50
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_format_code.py +0 -368
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_format_code_ported.py +0 -177
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_generate_tests.py +0 -305
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_hook_templates.py +0 -357
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_setup_hook_overlay.py +0 -95
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_validate_code.py +0 -867
- collab_runtime-0.2.9/tests/backend/unit/scripts/test_validate_code_ported.py +0 -237
- collab_runtime-0.2.9/tests/backend/unit/test_entrypoints_main_run.py +0 -83
- collab_runtime-0.2.9/tests/backend/unit/test_logging_config.py +0 -529
- collab_runtime-0.2.9/tests/backend/unit/test_main_watch_pid_file.py +0 -278
- collab_runtime-0.2.9/tests/conftest.py +0 -167
- collab_runtime-0.2.9/tests/frontend/__init__.py +0 -0
- collab_runtime-0.2.9/tests/frontend/jest/__init__.py +0 -0
- collab_runtime-0.2.9/tests/frontend/playwright/__init__.py +0 -0
- collab_runtime-0.2.9/tests/packaging/test_smoke_install.py +0 -76
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/LICENSE +0 -0
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/collab_runtime.egg-info/dependency_links.txt +0 -0
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/collab_runtime.egg-info/requires.txt +0 -0
- {collab_runtime-0.2.9 → collab_runtime-0.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: collab-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Collaborative file locking runtime
|
|
5
5
|
Author-email: KirilMT <kiril.mt95@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,7 +77,7 @@ pip install "collab-runtime>=0.2.2"
|
|
|
77
77
|
|
|
78
78
|
### 1 — Create the Database Schema
|
|
79
79
|
|
|
80
|
-
In your Supabase project, open **SQL Editor** and run the contents of [`schema.sql`](https://github.com/KirilMT/collab/blob/main/schema.sql).
|
|
80
|
+
In your Supabase project, open **SQL Editor** and run the contents of [`supabase/schema.sql`](https://github.com/KirilMT/collab/blob/main/supabase/schema.sql).
|
|
81
81
|
|
|
82
82
|
This creates the `file_locks` table, `file_locks_history` audit table, the atomic `acquire_lock()` RPC, Row Level Security policies, and Realtime publication.
|
|
83
83
|
|
|
@@ -164,7 +164,7 @@ The optional VS Code extension provides lock-on-open warnings, a status bar indi
|
|
|
164
164
|
**Install from source** (extension is bundled in the [GitHub repository](https://github.com/KirilMT/collab)):
|
|
165
165
|
|
|
166
166
|
1. Press `F1` → **Developer: Install Extension from Location...**
|
|
167
|
-
2. Select the `vscode
|
|
167
|
+
2. Select the `editors/vscode/collab-locks/` directory
|
|
168
168
|
3. Reload VS Code
|
|
169
169
|
|
|
170
170
|
Once installed, the extension automatically starts and stops the background daemon with your VS Code window.
|
|
@@ -198,7 +198,7 @@ Lock release follows the same path in reverse and writes an entry to `file_locks
|
|
|
198
198
|
|
|
199
199
|
- Lock correctness is enforced at the database level via atomic RPC — no client-side race conditions.
|
|
200
200
|
- Force-release requires `SUPABASE_SERVICE_ROLE_KEY`; regular releases are scoped to the owning developer.
|
|
201
|
-
- Row Level Security (RLS) is configured on all tables via `schema.sql`.
|
|
201
|
+
- Row Level Security (RLS) is configured on all tables via `supabase/schema.sql`.
|
|
202
202
|
- Never commit secrets; use `.env` for local configuration only.
|
|
203
203
|
|
|
204
204
|
---
|
|
@@ -17,14 +17,14 @@ Prevents merge conflicts by automatically locking files when a developer starts
|
|
|
17
17
|
|
|
18
18
|
### 1. Create the Database Schema
|
|
19
19
|
|
|
20
|
-
Open your Supabase project's **SQL Editor** and run the contents of `schema.sql`:
|
|
20
|
+
Open your Supabase project's **SQL Editor** and run the contents of `supabase/schema.sql`:
|
|
21
21
|
|
|
22
22
|
**Steps:**
|
|
23
23
|
|
|
24
24
|
1. Open your Supabase project dashboard
|
|
25
25
|
2. Navigate to **SQL Editor** (left sidebar)
|
|
26
26
|
3. Click **New Query**
|
|
27
|
-
4. Copy-paste the full contents of [schema.sql](schema.sql) from this repository
|
|
27
|
+
4. Copy-paste the full contents of [supabase/schema.sql](supabase/schema.sql) from this repository
|
|
28
28
|
5. Click **Run**
|
|
29
29
|
|
|
30
30
|
This creates:
|
|
@@ -53,7 +53,7 @@ One command handles everything — dependencies, `.env` configuration, and IDE i
|
|
|
53
53
|
**Linux/macOS (Bash):**
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
./scripts/setup.sh
|
|
56
|
+
./scripts/setup-dev.sh
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
**End-User Install (via pip):**
|
|
@@ -102,8 +102,8 @@ Once setup is complete, test the locking system:
|
|
|
102
102
|
**Terminal 1 (Lock a file):**
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
collab acquire
|
|
106
|
-
collab status
|
|
105
|
+
collab acquire collab/main.py --reason "Testing locking system"
|
|
106
|
+
collab status collab/main.py
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
**Terminal 2 (View lock in another session):**
|
|
@@ -115,7 +115,7 @@ collab active
|
|
|
115
115
|
**Then release the lock:**
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
collab release
|
|
118
|
+
collab release collab/main.py
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
**View real-time lock changes:**
|
|
@@ -202,11 +202,11 @@ The extension is primarily distributed via the **Collab Runtime** Python package
|
|
|
202
202
|
**Manual Install (from source):**
|
|
203
203
|
|
|
204
204
|
1. Press `F1` -> `Developer: Install Extension from Location...`
|
|
205
|
-
2. Select `vscode
|
|
205
|
+
2. Select `editors/vscode/collab-locks/`
|
|
206
206
|
3. Reload VS Code
|
|
207
207
|
|
|
208
208
|
**CLI Install:**
|
|
209
|
-
|
|
209
|
+
Production `scripts/setup.ps1` / `scripts/setup.sh` download the latest release `.vsix` and install it when a supported editor CLI is on `PATH`. **Development setup** (`scripts/setup-dev.ps1` / `scripts/setup-dev.sh`) repeats this with stronger IDE detection and resolves `code` / `cursor` (and siblings) from common install locations on Windows and macOS when they are not on `PATH`, which fixes installs from integrated terminals (for example **Cursor**).
|
|
210
210
|
|
|
211
211
|
### Features
|
|
212
212
|
|
|
@@ -279,7 +279,7 @@ if (Test-Path $pidPath) {
|
|
|
279
279
|
|
|
280
280
|
## Database Schema
|
|
281
281
|
|
|
282
|
-
The full schema is in `schema.sql` and includes:
|
|
282
|
+
The full schema is in `supabase/schema.sql` and includes:
|
|
283
283
|
|
|
284
284
|
- `file_locks` for active locks.
|
|
285
285
|
- `file_locks_history` for audit/history.
|
|
@@ -302,7 +302,7 @@ The full schema is in `schema.sql` and includes:
|
|
|
302
302
|
|
|
303
303
|
```
|
|
304
304
|
collab/
|
|
305
|
-
├──
|
|
305
|
+
├── collab/
|
|
306
306
|
│ ├── lock_client.py # CLI entry point
|
|
307
307
|
│ ├── live_locks_watcher.py # Background watcher
|
|
308
308
|
│ ├── main.py # CLI orchestration + module entry point
|
|
@@ -315,18 +315,24 @@ collab/
|
|
|
315
315
|
│ │ ├── functional/ # Functional tests
|
|
316
316
|
│ │ ├── integration/ # Integration tests
|
|
317
317
|
│ │ ├── security/ # Security tests
|
|
318
|
-
│ │
|
|
319
|
-
│ │ └── reliability/ # Reliability tests
|
|
318
|
+
│ │ └── (performance/ and reliability/ removed — empty placeholders deleted for clean/optimized structure)
|
|
320
319
|
│ └── frontend/
|
|
321
|
-
│
|
|
322
|
-
│ └── playwright/ # Frontend e2e placeholder
|
|
320
|
+
│ └── playwright/ # E2E + visual regression (config, CI job, helpers, snapshots, deterministic fixtures)
|
|
323
321
|
├── scripts/
|
|
324
|
-
│ ├── setup-dev.ps1 # Windows setup
|
|
322
|
+
│ ├── setup-dev.ps1 # Windows dev setup
|
|
325
323
|
│ ├── setup.sh # Linux/macOS setup
|
|
324
|
+
│ ├── git-hooks/ # Collab git hook templates
|
|
325
|
+
│ ├── install_hooks.sh # Installs templates into .git/hooks
|
|
326
326
|
│ ├── format_code.py # Code formatter
|
|
327
327
|
│ ├── validate_code.py # CI validator
|
|
328
328
|
│ └── cleanup.py # Cache cleanup
|
|
329
|
-
├──
|
|
329
|
+
├── supabase/
|
|
330
|
+
│ └── schema.sql # Supabase database schema
|
|
331
|
+
├── editors/
|
|
332
|
+
│ ├── vscode/collab-locks/ # VS Code / Cursor extension
|
|
333
|
+
│ └── pycharm/ # PyCharm run configuration template
|
|
334
|
+
├── docs/
|
|
335
|
+
│ └── pypi/README.md # PyPI package readme
|
|
330
336
|
├── pyproject.toml # Package configuration
|
|
331
337
|
└── README.md # This file
|
|
332
338
|
```
|
|
@@ -463,6 +469,20 @@ MIT License — see LICENSE file for details.
|
|
|
463
469
|
|
|
464
470
|
---
|
|
465
471
|
|
|
472
|
+
## Documentation
|
|
473
|
+
|
|
474
|
+
| Document | Description |
|
|
475
|
+
| --------------------------------------------- | ---------------------------------------- |
|
|
476
|
+
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design and data flow |
|
|
477
|
+
| [API.md](docs/API.md) | CLI overview and environment variables |
|
|
478
|
+
| [CLI_REFERENCE.md](docs/CLI_REFERENCE.md) | Full command reference |
|
|
479
|
+
| [SECURITY.md](docs/SECURITY.md) | Subprocess hardening and secret handling |
|
|
480
|
+
| [PERFORMANCE.md](docs/PERFORMANCE.md) | Validation and watcher tuning |
|
|
481
|
+
| [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common issues and fixes |
|
|
482
|
+
| [collab_roadmap.md](docs/collab_roadmap.md) | Future enhancements |
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
466
486
|
## Support
|
|
467
487
|
|
|
468
488
|
For issues, questions, or feature requests:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Collab runtime package (published on PyPI as ``collab-runtime``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
__all__ = ["__version__"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _installed_version(dist_name: str = "collab-runtime") -> Optional[str]:
|
|
11
|
+
try:
|
|
12
|
+
from importlib.metadata import version as _ver
|
|
13
|
+
except Exception:
|
|
14
|
+
try:
|
|
15
|
+
from importlib_metadata import version as _ver # type: ignore
|
|
16
|
+
except Exception:
|
|
17
|
+
return None
|
|
18
|
+
try:
|
|
19
|
+
return _ver(dist_name)
|
|
20
|
+
except Exception:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__version__ = _installed_version("collab-runtime") or "0.0.0"
|
|
@@ -489,53 +489,74 @@
|
|
|
489
489
|
<span>Collaborative Explorer</span>
|
|
490
490
|
</div>
|
|
491
491
|
<div class="nav-right">
|
|
492
|
-
<span id="last-update" class="chip">Not synced yet</span>
|
|
493
|
-
<span id="
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
<
|
|
492
|
+
<span id="last-update" class="chip" data-testid="last-update">Not synced yet</span>
|
|
493
|
+
<span id="project-info"
|
|
494
|
+
class="chip hidden text-muted small"
|
|
495
|
+
data-testid="project-info"></span>
|
|
496
|
+
<span id="user-info" class="chip hidden" data-testid="user-info"></span>
|
|
497
|
+
<button id="nav-locks"
|
|
498
|
+
class="btn btn-sm btn-primary"
|
|
499
|
+
type="button"
|
|
500
|
+
data-testid="nav-locks">Active Locks</button>
|
|
501
|
+
<button id="nav-history"
|
|
502
|
+
class="btn btn-sm btn-outline-primary"
|
|
503
|
+
type="button"
|
|
504
|
+
data-testid="nav-history">History Locks</button>
|
|
505
|
+
<button id="sync-btn"
|
|
506
|
+
class="btn btn-sm btn-outline-secondary"
|
|
507
|
+
type="button"
|
|
508
|
+
data-testid="sync-btn">
|
|
497
509
|
<i class="fas fa-sync-alt me-1"></i>Sync
|
|
498
510
|
</button>
|
|
499
511
|
</div>
|
|
500
512
|
</header>
|
|
501
513
|
<main class="app-main">
|
|
502
|
-
<section id="setup-view" class="setup-view hidden">
|
|
514
|
+
<section id="setup-view" class="setup-view hidden" data-testid="setup-view">
|
|
503
515
|
<h4 class="mb-2">Connect To Supabase</h4>
|
|
504
516
|
<p class="text-muted mb-3">Configure credentials in .env and restart the dashboard server.</p>
|
|
505
517
|
<pre class="bg-light border rounded p-3 mb-0">SUPABASE_URL=https://your-project.supabase.co
|
|
506
518
|
SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
507
519
|
</section>
|
|
508
|
-
<section id="locks-page"
|
|
520
|
+
<section id="locks-page"
|
|
521
|
+
class="page-view hidden"
|
|
522
|
+
aria-label="locks view"
|
|
523
|
+
data-testid="locks-page">
|
|
509
524
|
<div class="page-head">
|
|
510
525
|
<h2 class="page-title">
|
|
511
526
|
<i class="fas fa-lock"></i>Active Locks
|
|
512
527
|
</h2>
|
|
513
528
|
</div>
|
|
514
|
-
<div class="stats-grid"
|
|
515
|
-
|
|
529
|
+
<div class="stats-grid"
|
|
530
|
+
aria-label="dashboard stats"
|
|
531
|
+
data-testid="stats-grid">
|
|
532
|
+
<article class="stat-card" data-testid="stat-card-active">
|
|
516
533
|
<div class="stat-shell">
|
|
517
534
|
<span class="stat-icon stat-icon-lock"><i class="fas fa-lock"></i></span>
|
|
518
535
|
<div>
|
|
519
536
|
<p class="stat-label">Active Locks</p>
|
|
520
|
-
<p class="stat-value" id="stat-active">0</p>
|
|
537
|
+
<p class="stat-value" id="stat-active" data-testid="stat-active">0</p>
|
|
521
538
|
</div>
|
|
522
539
|
</div>
|
|
523
540
|
</article>
|
|
524
|
-
<article class="stat-card">
|
|
541
|
+
<article class="stat-card" data-testid="stat-card-releases">
|
|
525
542
|
<div class="stat-shell">
|
|
526
543
|
<span class="stat-icon stat-icon-release"><i class="fas fa-check-double"></i></span>
|
|
527
544
|
<div>
|
|
528
545
|
<p class="stat-label">Releases Today</p>
|
|
529
|
-
<p class="stat-value stat-value-success"
|
|
546
|
+
<p class="stat-value stat-value-success"
|
|
547
|
+
id="stat-releases"
|
|
548
|
+
data-testid="stat-releases">0</p>
|
|
530
549
|
</div>
|
|
531
550
|
</div>
|
|
532
551
|
</article>
|
|
533
|
-
<article class="stat-card">
|
|
552
|
+
<article class="stat-card" data-testid="stat-card-avg">
|
|
534
553
|
<div class="stat-shell">
|
|
535
554
|
<span class="stat-icon stat-icon-avg"><i class="fas fa-bolt"></i></span>
|
|
536
555
|
<div>
|
|
537
556
|
<p class="stat-label">Avg Hold Time</p>
|
|
538
|
-
<p class="stat-value stat-value-info"
|
|
557
|
+
<p class="stat-value stat-value-info"
|
|
558
|
+
id="stat-avg"
|
|
559
|
+
data-testid="stat-avg">0m</p>
|
|
539
560
|
</div>
|
|
540
561
|
</div>
|
|
541
562
|
</article>
|
|
@@ -553,7 +574,7 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
553
574
|
<th class="text-end">Action</th>
|
|
554
575
|
</tr>
|
|
555
576
|
</thead>
|
|
556
|
-
<tbody id="active-locks-body">
|
|
577
|
+
<tbody id="active-locks-body" data-testid="active-locks-body">
|
|
557
578
|
<tr>
|
|
558
579
|
<td colspan="6" class="text-center py-4 text-muted">Connecting...</td>
|
|
559
580
|
</tr>
|
|
@@ -562,7 +583,10 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
562
583
|
</div>
|
|
563
584
|
</div>
|
|
564
585
|
</section>
|
|
565
|
-
<section id="history-page"
|
|
586
|
+
<section id="history-page"
|
|
587
|
+
class="page-view hidden"
|
|
588
|
+
aria-label="history view"
|
|
589
|
+
data-testid="history-page">
|
|
566
590
|
<div class="page-head">
|
|
567
591
|
<h2 class="page-title">
|
|
568
592
|
<i class="fas fa-clock-rotate-left"></i>Lock History
|
|
@@ -583,7 +607,7 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
583
607
|
<th>Outcome</th>
|
|
584
608
|
</tr>
|
|
585
609
|
</thead>
|
|
586
|
-
<tbody id="history-body">
|
|
610
|
+
<tbody id="history-body" data-testid="history-body">
|
|
587
611
|
<tr>
|
|
588
612
|
<td colspan="8" class="text-center py-4 text-muted">Loading history...</td>
|
|
589
613
|
</tr>
|
|
@@ -626,13 +650,16 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
626
650
|
</div>
|
|
627
651
|
</div>
|
|
628
652
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
653
|
+
<script src="dashboard-format.js"></script>
|
|
629
654
|
<script>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
655
|
+
let runtimeCfg = window.__SUPABASE_CONFIG__ || {};
|
|
656
|
+
let SUPABASE_URL = "";
|
|
657
|
+
let SUPABASE_KEY = "";
|
|
658
|
+
let SUPABASE_USER = null;
|
|
659
|
+
let IS_ADMIN = false;
|
|
660
|
+
let SUPABASE_MODE = false;
|
|
661
|
+
let supabaseClientFingerprint = "";
|
|
662
|
+
const RUNTIME_CONFIG_PATH = "/collab-runtime-config.json";
|
|
636
663
|
|
|
637
664
|
const PAGE_SIZE = 25;
|
|
638
665
|
const HISTORY_PREFETCH_GAP_PX = 120;
|
|
@@ -665,56 +692,14 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
665
692
|
}
|
|
666
693
|
}
|
|
667
694
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function formatTime24(dt) {
|
|
677
|
-
return dt.toLocaleTimeString([], {
|
|
678
|
-
hour: "2-digit",
|
|
679
|
-
minute: "2-digit",
|
|
680
|
-
hour12: false
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
function formatDateTime24(dt) {
|
|
685
|
-
return formatDateLong(dt) + " " + formatTime24(dt);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function formatDurationMinutes(totalMinutes) {
|
|
689
|
-
const rounded = Math.max(0, Math.round(Number(totalMinutes) || 0));
|
|
690
|
-
if (!Number.isFinite(rounded) || rounded <= 0) {
|
|
691
|
-
return "0m";
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const units = [
|
|
695
|
-
{ label: "mo", minutes: 30 * 24 * 60 },
|
|
696
|
-
{ label: "d", minutes: 24 * 60 },
|
|
697
|
-
{ label: "h", minutes: 60 },
|
|
698
|
-
{ label: "m", minutes: 1 }
|
|
699
|
-
];
|
|
700
|
-
|
|
701
|
-
let remaining = rounded;
|
|
702
|
-
const parts = [];
|
|
703
|
-
|
|
704
|
-
units.forEach((unit) => {
|
|
705
|
-
if (remaining >= unit.minutes) {
|
|
706
|
-
const value = Math.floor(remaining / unit.minutes);
|
|
707
|
-
remaining -= value * unit.minutes;
|
|
708
|
-
parts.push(String(value) + unit.label);
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
return parts.length ? parts.join(" ") : "0m";
|
|
713
|
-
}
|
|
695
|
+
const fmt = window.DashboardFormat;
|
|
696
|
+
const formatDateLong = fmt.formatDateLong;
|
|
697
|
+
const formatTime24 = fmt.formatTime24;
|
|
698
|
+
const formatDateTime24 = fmt.formatDateTime24;
|
|
699
|
+
const formatDurationMinutes = fmt.formatDurationMinutes;
|
|
714
700
|
|
|
715
701
|
function routeFromHash() {
|
|
716
|
-
|
|
717
|
-
return h === "history" ? "history" : "locks";
|
|
702
|
+
return fmt.routeFromHash(window.location.hash);
|
|
718
703
|
}
|
|
719
704
|
|
|
720
705
|
function navigate(page) {
|
|
@@ -764,14 +749,57 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
764
749
|
document.getElementById("nav-history").disabled = true;
|
|
765
750
|
}
|
|
766
751
|
|
|
752
|
+
function applyRuntimeConfig() {
|
|
753
|
+
SUPABASE_URL = runtimeCfg.url || "";
|
|
754
|
+
SUPABASE_KEY = runtimeCfg.serviceKey || runtimeCfg.anonKey || "";
|
|
755
|
+
SUPABASE_USER = runtimeCfg.user || null;
|
|
756
|
+
IS_ADMIN = !!runtimeCfg.serviceKey;
|
|
757
|
+
SUPABASE_MODE = !!(SUPABASE_URL && SUPABASE_KEY);
|
|
758
|
+
updateProjectInfo();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function updateProjectInfo() {
|
|
762
|
+
const el = document.getElementById("project-info");
|
|
763
|
+
if (!SUPABASE_URL) {
|
|
764
|
+
el.classList.add("hidden");
|
|
765
|
+
el.textContent = "";
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const host = new URL(SUPABASE_URL).hostname.replace(".supabase.co", "");
|
|
770
|
+
el.textContent = host;
|
|
771
|
+
el.title = "Supabase project: " + SUPABASE_URL;
|
|
772
|
+
el.classList.remove("hidden");
|
|
773
|
+
} catch (e) {
|
|
774
|
+
el.classList.add("hidden");
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function syncRuntimeConfig() {
|
|
779
|
+
try {
|
|
780
|
+
const resp = await fetch(RUNTIME_CONFIG_PATH, { cache: "no-store" });
|
|
781
|
+
if (resp.ok) {
|
|
782
|
+
const live = await resp.json();
|
|
783
|
+
if (live && live.url) {
|
|
784
|
+
runtimeCfg = live;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
console.debug("Runtime config fetch skipped", e);
|
|
789
|
+
}
|
|
790
|
+
applyRuntimeConfig();
|
|
791
|
+
}
|
|
792
|
+
|
|
767
793
|
function showMain() {
|
|
768
794
|
document.getElementById("setup-view").classList.add("hidden");
|
|
769
795
|
document.getElementById("locks-page").classList.remove("hidden");
|
|
770
796
|
document.getElementById("history-page").classList.remove("hidden");
|
|
797
|
+
const userInfo = document.getElementById("user-info");
|
|
771
798
|
if (SUPABASE_USER) {
|
|
772
|
-
const userInfo = document.getElementById("user-info");
|
|
773
799
|
userInfo.classList.remove("hidden");
|
|
774
800
|
userInfo.innerHTML = '<i class="fab fa-github me-1"></i>' + SUPABASE_USER;
|
|
801
|
+
} else {
|
|
802
|
+
userInfo.classList.add("hidden");
|
|
775
803
|
}
|
|
776
804
|
}
|
|
777
805
|
|
|
@@ -783,33 +811,40 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
783
811
|
'</td></tr>';
|
|
784
812
|
}
|
|
785
813
|
|
|
786
|
-
function
|
|
814
|
+
function loadSupabaseLibrary() {
|
|
787
815
|
return new Promise((resolve, reject) => {
|
|
788
816
|
if (window.supabase) {
|
|
789
|
-
|
|
790
|
-
supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
791
|
-
resolve();
|
|
792
|
-
} catch (e) {
|
|
793
|
-
reject(e);
|
|
794
|
-
}
|
|
817
|
+
resolve();
|
|
795
818
|
return;
|
|
796
819
|
}
|
|
797
|
-
|
|
798
820
|
const script = document.createElement("script");
|
|
799
821
|
script.src = "https://cdn.jsdelivr.net/npm/@supabase/supabase-js/dist/umd/supabase.min.js";
|
|
800
|
-
script.onload = () =>
|
|
801
|
-
try {
|
|
802
|
-
supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
803
|
-
resolve();
|
|
804
|
-
} catch (e) {
|
|
805
|
-
reject(e);
|
|
806
|
-
}
|
|
807
|
-
};
|
|
822
|
+
script.onload = () => resolve();
|
|
808
823
|
script.onerror = reject;
|
|
809
824
|
document.head.appendChild(script);
|
|
810
825
|
});
|
|
811
826
|
}
|
|
812
827
|
|
|
828
|
+
function buildSupabaseClient() {
|
|
829
|
+
return window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function ensureSupabaseClient() {
|
|
833
|
+
await syncRuntimeConfig();
|
|
834
|
+
if (!SUPABASE_MODE) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
const fingerprint =
|
|
838
|
+
SUPABASE_URL + "|" + (runtimeCfg.serviceKey ? "service" : "anon");
|
|
839
|
+
if (supabaseClient && supabaseClientFingerprint === fingerprint) {
|
|
840
|
+
return true;
|
|
841
|
+
}
|
|
842
|
+
await loadSupabaseLibrary();
|
|
843
|
+
supabaseClient = buildSupabaseClient();
|
|
844
|
+
supabaseClientFingerprint = fingerprint;
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
|
|
813
848
|
function subscribeRealtime() {
|
|
814
849
|
if (!supabaseClient) {
|
|
815
850
|
return;
|
|
@@ -840,6 +875,10 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
840
875
|
const syncBtn = document.getElementById("sync-btn");
|
|
841
876
|
syncBtn.disabled = true;
|
|
842
877
|
try {
|
|
878
|
+
if (!(await ensureSupabaseClient())) {
|
|
879
|
+
showSetup();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
843
882
|
const { data, error } = await supabaseClient
|
|
844
883
|
.from("file_locks")
|
|
845
884
|
.select("*")
|
|
@@ -864,7 +903,7 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
864
903
|
console.error(e);
|
|
865
904
|
showLocksError("Unable to fetch active locks.");
|
|
866
905
|
} finally {
|
|
867
|
-
syncBtn.disabled =
|
|
906
|
+
syncBtn.disabled = !SUPABASE_MODE;
|
|
868
907
|
}
|
|
869
908
|
}
|
|
870
909
|
|
|
@@ -949,6 +988,9 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
949
988
|
|
|
950
989
|
historyLoading = true;
|
|
951
990
|
try {
|
|
991
|
+
if (!(await ensureSupabaseClient())) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
952
994
|
const from = historyOffset;
|
|
953
995
|
const to = historyOffset + PAGE_SIZE - 1;
|
|
954
996
|
const { data, error } = await supabaseClient
|
|
@@ -1102,17 +1144,15 @@ SUPABASE_ANON_KEY=your_anon_key</pre>
|
|
|
1102
1144
|
async function init() {
|
|
1103
1145
|
releaseModal = new bootstrap.Modal(document.getElementById("releaseModal"));
|
|
1104
1146
|
wireEvents();
|
|
1105
|
-
|
|
1106
|
-
if (!SUPABASE_MODE) {
|
|
1107
|
-
showSetup();
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
showMain();
|
|
1112
|
-
applyRoute();
|
|
1147
|
+
applyRuntimeConfig();
|
|
1113
1148
|
|
|
1114
1149
|
try {
|
|
1115
|
-
await
|
|
1150
|
+
if (!(await ensureSupabaseClient())) {
|
|
1151
|
+
showSetup();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
showMain();
|
|
1155
|
+
applyRoute();
|
|
1116
1156
|
await refreshLocks();
|
|
1117
1157
|
if (routeFromHash() === "history") {
|
|
1118
1158
|
resetHistory();
|