diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index a207de01..cb2a696d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -37,6 +37,14 @@ updates:
major-updates:
update-types:
- "major"
+ ignore:
+ # typescript-eslint declares `typescript: >=4.8.4 <6.1.0`, and TypeScript 7
+ # removed `ts.Extension`, which @typescript-eslint/typescript-estree reads
+ # at import time. Bumping to 7 makes `eslint .` fail to load its own config,
+ # so `npm run lint` cannot run at all. Drop this once typescript-eslint
+ # supports TypeScript 7.
+ - dependency-name: "typescript"
+ update-types: ["version-update:semver-major"]
# Docker base images (docker/Dockerfile + docker-compose / compose-dev)
- package-ecosystem: "docker"
diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml
index 9abbddc3..05b5fbda 100644
--- a/.github/workflows/beta-release.yml
+++ b/.github/workflows/beta-release.yml
@@ -157,7 +157,7 @@ jobs:
docker:
needs: [prep, verify, create-release]
- if: ${{ always() && needs.prep.outputs.dev_branch != '' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
+ if: ${{ always() && needs.prep.outputs.dev_branch != '' && needs.verify.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.beta_version }}
diff --git a/.github/workflows/crowdin-sync.yml b/.github/workflows/crowdin-sync.yml
index 8ab185cd..2a63ea1e 100644
--- a/.github/workflows/crowdin-sync.yml
+++ b/.github/workflows/crowdin-sync.yml
@@ -34,7 +34,7 @@ jobs:
token: ${{ secrets.GHCR_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v6
+ uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 6a465bce..462a69ed 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -60,7 +60,7 @@ jobs:
platforms: linux/amd64,linux/arm64
- name: Setup Docker Buildx
- uses: useblacksmith/setup-docker-builder@v1
+ uses: useblacksmith/setup-docker-builder@v2
- name: Determine tags
id: tags
diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml
index b09e52c6..dd56071a 100644
--- a/.github/workflows/electron.yml
+++ b/.github/workflows/electron.yml
@@ -559,7 +559,7 @@ jobs:
CHECKSUM=$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')
mkdir -p homebrew-generated
- cp packaging/Casks/termix.rb homebrew-generated/termix.rb
+ cp Casks/termix.rb homebrew-generated/termix.rb
sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-generated/termix.rb
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb
@@ -894,7 +894,7 @@ jobs:
mkdir -p homebrew-submission/Casks/t
- cp packaging/Casks/termix.rb homebrew-submission/Casks/t/termix.rb
+ cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb
sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 123f4dcb..cfed886b 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -24,8 +24,10 @@ jobs:
- name: Install dependencies
run: npm ci
- - name: Run ESLint
- run: npx eslint .
+ - name: Lint
+ # npm run lint, not npx eslint — the script also checks that the
+ # generated dialect schemas match schema.ts, which eslint cannot see.
+ run: npm run lint
- name: Run Prettier check
run: npx prettier --check .
@@ -35,3 +37,76 @@ jobs:
- name: Build
run: npm run build
+
+ database-dialects:
+ name: Postgres and MySQL
+ runs-on: blacksmith-2vcpu-ubuntu-2404
+
+ # The test suite only ever sees SQLite. Everything that differs per engine —
+ # the RETURNING replacements, the read-then-write transactions, the
+ # migrations themselves — is only covered here, against real servers.
+ services:
+ postgres:
+ image: postgres:16
+ env:
+ POSTGRES_USER: termix
+ POSTGRES_PASSWORD: termix
+ POSTGRES_DB: termix_test
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ mysql:
+ image: mysql:8
+ env:
+ MYSQL_ROOT_PASSWORD: termix
+ MYSQL_DATABASE: termix_test
+ MYSQL_USER: termix
+ MYSQL_PASSWORD: termix
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd "mysqladmin ping -h 127.0.0.1 -ptermix"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 10
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version-file: ".nvmrc"
+ cache: "npm"
+
+ - name: Install dependencies
+ run: npm ci
+
+ # Each run applies the migrations to an empty database first, so a
+ # migration that does not apply cleanly fails the build.
+ - name: Verify Postgres
+ run: npm run verify:dialect -- postgres://termix:termix@127.0.0.1:5432/termix_test
+
+ - name: Verify MySQL
+ run: npm run verify:dialect -- mysql://termix:termix@127.0.0.1:3306/termix_test
+
+ # The same repository suite the SQLite run executes, pointed at each
+ # engine. This is where a dialect difference in a query shows up as a
+ # failing assertion rather than as a bug report.
+ - name: Repository tests on Postgres
+ env:
+ TEST_DIALECT: postgres
+ TEST_DATABASE_URL: postgres://termix:termix@127.0.0.1:5432/termix_test
+ run: npx vitest run src/backend/tests/database/repositories --no-file-parallelism
+
+ - name: Repository tests on MySQL
+ env:
+ TEST_DIALECT: mysql
+ TEST_DATABASE_URL: mysql://termix:termix@127.0.0.1:3306/termix_test
+ run: npx vitest run src/backend/tests/database/repositories --no-file-parallelism
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d4b51618..3560f2ce 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -395,10 +395,10 @@ jobs:
git fetch origin main
git checkout -B main origin/main
- sed -i "s|version \".*\"|version \"$VERSION\"|g" packaging/Casks/termix.rb
- sed -i "s|sha256 \".*\"|sha256 \"$DMG_SHA256\"|g" packaging/Casks/termix.rb
+ sed -i "s|version \".*\"|version \"$VERSION\"|g" Casks/termix.rb
+ sed -i "s|sha256 \".*\"|sha256 \"$DMG_SHA256\"|g" Casks/termix.rb
- git add packaging/Casks/termix.rb
+ git add Casks/termix.rb
if git diff --cached --quiet; then
echo "Cask already up to date."
exit 0
diff --git a/.prettierignore b/.prettierignore
index befe2e1f..8cf32b0d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -17,3 +17,6 @@ db
*.min.js
*.min.css
openapi.json
+
+# Generated by drizzle-kit; formatting is the tool's own
+drizzle/
diff --git a/packaging/Casks/termix.rb b/Casks/termix.rb
similarity index 100%
rename from packaging/Casks/termix.rb
rename to Casks/termix.rb
diff --git a/README.md b/README.md
index b63c8678..848843e3 100644
--- a/README.md
+++ b/README.md
@@ -317,7 +317,7 @@ You can also run the Termix server on a cloud VPS instead of inside your own net
Termix sends a small anonymous usage ping once every 24 hours to help understand how many instances are running and which features are actually used. This only includes a randomly generated instance ID, a count of users and hosts, the app version, and whether certain features (terminal, file manager, tunnels, docker, etc.) were used in the last 24 hours. It never includes usernames, hostnames, IP addresses, credentials, or any other identifying or connection data.
-This is opt-out and enabled by default. You can disable it at any time in Admin Settings under **General**.
+This is opt-out and enabled by default. You can disable it at any time in Admin Settings under General, or set `ENABLE_TELEMETRY=false` to turn it off before you ever spin-up Termix.
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index caaa91f2..a855ec14 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -12,62 +12,59 @@ https://youtu.be/g0QjNdV3YYY
-- Added simple telemetrics to PostHog (user count, total hosts across users, and version metrics).
-- Reworked Electron desktop app to run standalone-first with a now optional sync to a remote Termix server.
-- Added support for starting connections locally or from the remote server on desktop app.
-- Added support for custom key shortcuts.
-- Added support for more MFA types (SSH-only).
-- Added multiplayer/shared sessions for terminals and remote desktop (share via link or user).
-- Added support for logging into SSH hosts that require multi-factor authentication (like Duo or JumpCloud push/TOTP prompts).
-- Added the option to convert a Quick Connect session into a saved host after connecting.
-- Added an export option for sharing host entries without credentials, so a host list can be shared without leaking passwords or keys.
-- Unified the folder picker across hosts, credentials, and snippets so they all use the same searchable, create-in-place selector.
-- Allowed pasting into the key recording field from the clipboard.
-- Allowed sharing hosts that use authentication type "none".
-- Allowed setting authentication type "none" on RDP hosts.
-- Added the option to show two or more sidebar panels open at the same time.
-- Added global custom themes that can be applied across all hosts instead of per host.
-- Added a button to quickly create a Credentials entry from a host's existing authentication details.
-- Added persistent split screen, so your layout and assigned tabs are restored after closing and reopening the app.
-- Added tag matching to host search, so searching now matches tags as well as hostnames.
-- Brought back the ability to collapse snippets.
-- Added the ability to assign login credentials to an entire folder of hosts instead of one at a time.
-- Added the ability to share terminal, VNC, and RDP sessions with other users, including read-only and read-write modes.
-- Added the ability to share entire folders of hosts with other users instead of sharing hosts one by one.
-- Added the ability to make folders of hosts available to specific users instead of everyone recreating them.
-- Reworked SSH credentials to support both a password and an SSH key on the same credential, with an option to auto fill the password when prompted.
-- Added a custom group claim option for OIDC login, useful for identity providers like Zitadel that don't use a plain "groups" claim.
+- Added support for multi disk usage in file manager/host metrics
+- Added better Ctrl + F terminal search
+- Added right click menu on app rail to pin sidebar faster
+- Support for overriding shared SSH credential
+- Added mapping for OIDC provider groups to RBAC roles
+- Added host export dialog for more customizable host exporting
+- Initial groundwork for supporting more database types (postgres and mysql)
+- Added audit log export (CSV/NDJSON) and optional live forwarding to a SIEM
+- Added configurable audit log retention by age and row count
+- Audit entries for file manager, RDP/VNC/Telnet, Docker and tunnel sessions
+- Encrypted SSO secrets instead of BASE64 encoding them
+- Added support for Tailscale SSH check mode with in-terminal browser authentication
+
-- Fixed the Add Channel dialog failing with "config is required" when adding Webhook or ntfy alert channels.
-- Fixed font size and UI scaling being too small even at the largest setting on high resolution displays.
-- Fixed the Docker integration not working on hosts using authentication type "none".
-- Fixed the latest Russian translation updates from Crowdin not being included in the app.
-- Fixed missing Nerd Font symbol support in the Android app.
-- Fixed credential changes on RDP hosts not saving properly.
-- Fixed credential folders not showing up in the folder dropdown when editing a credential.
-- Fixed RDP hosts not using their stored credential and falling back to a direct connection.
-- Fixed Cmd + scroll on Mac resizing the terminal instead of scrolling.
-- Fixed text repeating itself in the terminal when typing with a wireless keyboard on Android.
-- Fixed RDP connections failing when going through a jump host.
-- Fixed SSH connections failing when going through a jump host in some setups.
-- Fixed OIDC login failing with a database error when the identity provider didn't return a client ID.
-- Fixed SSH client-to-server tunnels failing with an authentication error.
-- Fixed Host Metrics disk usage only showing the root filesystem and ignoring other mounted disks.
-- Fixed Android navigation buttons covering the terminal's top bar keys.
-- Fixed Proxmox discovery importing DHCP LXC containers with an IP of 0.0.0.0 instead of their real address.
-- Fixed VNC connections to macOS Screen Sharing hanging after the handshake instead of connecting.
-- Fixed File Manager delete still failing on Windows hosts running PowerShell 5.1.
-- Fixed the terminal dropping characters while typing on iOS.
-- Fixed SSH lines like "[username@host]" being wrongly highlighted as a log level and breaking output formatting.
-- Fixed RDP touch mode on Android not registering taps as clicks.
-- Fixed VNC connections still failing due to a guacd protocol version mismatch.
-- Guacamole tab showing "connecting" instead of rendering the desktop.
-- Fixed tmux not using Tailscale when starting connections.
-- Fixed an invalid websocket frame from causing code 10006 crash triggering restart loop.
-- Remove chacha20-poly1305 without native ssh2 binding.
-- Corrected SSRF blocklist from false-positive on all IPv4.
-- Fixed terminal background image incorrectly displaying.
+- Hardened nginx headers/asset caching
+- Deleting an account no longer deletes its audit entries and session recordings
+- Made logger display expanded error messages
+- Removed phantom port knocking
+- Fixed Proxmox guest discovery failures over jump host
+- Compare sync cursors independently of timestamp layout
+- Fixed sync deleting not reaching other side
+- Remote sync stalling after first pass and never propagating deletions
+- DB_FILE_ENCRYPTION variable loading DB file as empty
+- Removed unneeded field encryption boundaries
+- SSH login alerts being dropped silently
+- Honor lookupOptions.all in custom DNS lookup hook
+- Jump host SOCKS proxy settings being ignored
+- Jump host tunnels not reachable by guacd
+- Per-host RDP/VNC recording flags being ignored
+- RDP sessions not using the configured resolution
+- OIDC login failing with unverifiable ID tokens or JWKs without alg
+- Refuse to start with an empty database when data exists elsewhere
+- Database not persisting during container shutdown
+- Host command history setting not saving
+- Desktop preference sync and remote sync account identity
+- Desktop guacd calls not routed to the connected remote server
+- File manager navigation getting stuck after permission errors
+- Read-only shared hosts could be dragged into folders
+- Terminal highlighting breaking inside split control strings
+- Windows terminal Tab key and Android hardware keyboard keys
+- tmux monitor failing on Tailscale-authenticated hosts
+- Database export not staying same-origin on localhost
+- Snippet execution results not reported correctly
+- Shared hosts appearing twice
+- Wake-on-LAN broadcast address being dropped
+- Sharing an empty folder was rejected
+- Remote sync losing references between linked records
+- Desktop app failing to find its backend on some architectures
+- Centralized outbound address validation for homepage proxy requests
+- Default font size to medium instead of large
+- Tailscale hosts hanging on connect when the tailnet ACL requires a periodic check
+
diff --git a/biome.json b/biome.json
index 68bf4687..d754b9a2 100644
--- a/biome.json
+++ b/biome.json
@@ -4,7 +4,7 @@
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
- "defaultBranch": "dev-2.5.0"
+ "defaultBranch": "dev-2.6.1"
},
"files": {
"ignoreUnknown": true,
diff --git a/docker/Dockerfile b/docker/Dockerfile
index f4028795..a0bfa6b2 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -76,6 +76,9 @@ COPY --chown=node:node --from=frontend-builder /app/dist /app/html
COPY --chown=node:node --from=production-deps /app/node_modules /app/node_modules
COPY --chown=node:node --from=backend-builder /app/dist/backend ./dist/backend
COPY --chown=node:node package.json ./
+# Schema for Postgres and MySQL. Unused by the default SQLite deployment, which
+# builds its tables at startup instead.
+COPY --chown=node:node drizzle ./drizzle
VOLUME ["/app/data"]
diff --git a/docker/compose-dev.yml b/docker/compose-dev.yml
index 14b703a8..3ed77957 100644
--- a/docker/compose-dev.yml
+++ b/docker/compose-dev.yml
@@ -13,6 +13,7 @@ services:
PORT: "8080"
NODE_ENV: development
GUACD_HOST: "guacd-dev"
+ GUACD_TUNNEL_HOST: "termix-dev"
GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole"
depends_on:
- guacd-dev
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index eed4d172..856f827b 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -10,6 +10,7 @@ services:
environment:
PORT: "8080"
GUACD_HOST: "guacd"
+ GUACD_TUNNEL_HOST: "termix"
GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole"
depends_on:
- guacd
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index 917ea608..17ca6aab 100644
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -163,8 +163,4 @@ else
echo "Warning: package.json not found"
fi
-node dist/backend/backend/starter.js
-
-echo "All services started"
-
-tail -f /dev/null
+exec node dist/backend/backend/starter.js
diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf
index ce22530d..fea90737 100644
--- a/docker/nginx-https.conf
+++ b/docker/nginx-https.conf
@@ -11,6 +11,8 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
+ server_tokens off;
+
access_log /tmp/nginx/access.log;
client_body_temp_path /tmp/nginx/client_body;
@@ -69,7 +71,6 @@ http {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
- add_header X-XSS-Protection "1; mode=block" always;
location ^~ /.well-known/acme-challenge/ {
root /app/data/acme-webroot;
@@ -80,6 +81,8 @@ http {
location = /sw.js {
root /app/html;
expires off;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
@@ -87,31 +90,64 @@ http {
location = /manifest.json {
root /app/html;
expires off;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ location ^~ /assets/ {
root /app/html;
expires 1y;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
+ location ^~ /fonts/ {
+ root /app/html;
+ expires 1y;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=31536000, immutable" always;
+ try_files $uri =404;
+ }
+
+ location ^~ /icons/ {
+ root /app/html;
+ expires 30d;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=2592000" always;
+ try_files $uri =404;
+ }
+
+ location ~* ^/[^/]+\.(js|css|png|jpe?g|gif|ico|svg|webp|woff2?|ttf|eot)$ {
+ root /app/html;
+ expires 30d;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=2592000" always;
+ try_files $uri =404;
+ }
+
+ location ~* \.map$ {
+ access_log off;
+ log_not_found off;
+ return 404;
+ }
+
location / {
root /app/html;
index index.html index.htm;
expires off;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri $uri/ /index.html;
}
- location ~* \.map$ {
- return 404;
- access_log off;
- log_not_found off;
- }
-
location ~ ^/users/sessions(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
@@ -372,7 +408,9 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location ~ ^/host/opkssh-callback(/.*)?$ {
@@ -387,7 +425,9 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location /host/ {
@@ -549,6 +589,8 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -570,6 +612,8 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -771,6 +815,7 @@ http {
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /app/html;
+ internal;
}
}
}
diff --git a/docker/nginx.conf b/docker/nginx.conf
index 235218b9..98aa8dcc 100644
--- a/docker/nginx.conf
+++ b/docker/nginx.conf
@@ -11,6 +11,8 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
+ server_tokens off;
+
access_log /tmp/nginx/access.log;
client_body_temp_path /tmp/nginx/client_body;
@@ -58,7 +60,6 @@ http {
absolute_redirect off;
add_header X-Content-Type-Options nosniff always;
- add_header X-XSS-Protection "1; mode=block" always;
location ^~ /.well-known/acme-challenge/ {
root /app/data/acme-webroot;
@@ -69,6 +70,7 @@ http {
location = /sw.js {
root /app/html;
expires off;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
@@ -76,31 +78,58 @@ http {
location = /manifest.json {
root /app/html;
expires off;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ location ^~ /assets/ {
root /app/html;
expires 1y;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
+ location ^~ /fonts/ {
+ root /app/html;
+ expires 1y;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=31536000, immutable" always;
+ try_files $uri =404;
+ }
+
+ location ^~ /icons/ {
+ root /app/html;
+ expires 30d;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=2592000" always;
+ try_files $uri =404;
+ }
+
+ location ~* ^/[^/]+\.(js|css|png|jpe?g|gif|ico|svg|webp|woff2?|ttf|eot)$ {
+ root /app/html;
+ expires 30d;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "public, max-age=2592000" always;
+ try_files $uri =404;
+ }
+
+ location ~* \.map$ {
+ access_log off;
+ log_not_found off;
+ return 404;
+ }
+
location / {
root /app/html;
index index.html index.htm;
expires off;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri $uri/ /index.html;
}
- location ~* \.map$ {
- return 404;
- access_log off;
- log_not_found off;
- }
-
location ~ ^/users/sessions(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
@@ -361,7 +390,8 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location ~ ^/host/opkssh-callback(/.*)?$ {
@@ -376,7 +406,8 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header X-Content-Type-Options nosniff always;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location /host/ {
@@ -538,6 +569,7 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -559,6 +591,7 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
+ add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -760,6 +793,7 @@ http {
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /app/html;
+ internal;
}
}
}
diff --git a/docs/database-backends.md b/docs/database-backends.md
new file mode 100644
index 00000000..0fdd4753
--- /dev/null
+++ b/docs/database-backends.md
@@ -0,0 +1,240 @@
+# Database backends
+
+Termix runs on SQLite by default. Postgres and MySQL are supported for
+self-hosted deployments; this document records how the three differ, because the
+differences are not only about SQL.
+
+## This is multi-backend, not a migration
+
+SQLite is not going away. The desktop app embeds its own backend and cannot ship
+a database server, so it will always run on SQLite. Postgres and MySQL exist for
+self-hosted deployments that need more than one process to reach the data —
+multiple replicas, an external backup story, or an existing database estate.
+
+Anything that assumes a single engine is wrong.
+
+## Where the schema comes from
+
+`src/backend/database/db/schema.ts` is the single source of truth, written
+against `drizzle-orm/sqlite-core`.
+
+`schema.pg.ts` and `schema.mysql.ts` are **generated** from it:
+
+```bash
+npm run schema:generate # rewrite the generated modules
+npm run schema:check # fail if they are out of date (runs as part of lint)
+```
+
+Never edit the generated files. `npm run lint` fails if they drift from the
+source, so a schema change that forgets to regenerate cannot reach main.
+
+The transforms are mechanical:
+
+| sqlite | postgres | mysql |
+| ------------------------------------------------ | ----------------- | ----------------------- |
+| `integer(…, { mode: "boolean" })` | `boolean` | `boolean` |
+| `integer(…).primaryKey({ autoIncrement: true })` | `serial` | `int().autoincrement()` |
+| `integer` | `integer` | `int` |
+| `real` | `doublePrecision` | `double` |
+| `text` used as a key | `varchar(255)` | `varchar(255)` |
+
+A column becomes `varchar` if it is a primary key, is unique, or sits on either
+end of a foreign key — MySQL cannot index an unbounded `TEXT`, and both sides of
+a foreign key must agree.
+
+## Durability
+
+On SQLite the database is loaded into memory and serialised back to an encrypted
+file, so every write needs an explicit flush. That is what the `onWrite` hook
+each repository receives is for.
+
+On Postgres and MySQL a committed write is already durable. No hook is installed
+at all — see `needsExplicitPersist` in `db/dialect.ts`.
+
+## Encryption: what changes, and what does not
+
+This is the part most likely to be misread, so it is spelled out.
+
+### Unchanged on every backend
+
+**Field-level encryption still applies.** Credentials and other sensitive values
+are encrypted in the application before they reach the database, under a
+per-user data key:
+
+- `ssh_data` — passwords, private keys, key passphrases, sudo/RDP/VNC/Telnet
+ secrets
+- `ssh_credentials` — passwords, private and public keys
+- `users` — TOTP secret and backup codes
+- `vault_tokens`, `opkssh_tokens`, `termix_identity_ca` — certificates and keys
+- `shared_host_secrets` — re-encrypted per recipient
+
+Installation-level secrets — the OIDC client secret and LDAP bind password —
+are encrypted under the system key, since they have no owning user and must be
+readable during login.
+
+This is the protection that matters most, and it is identical on all three
+engines.
+
+### Different on Postgres and MySQL
+
+**Whole-file encryption does not exist.** On SQLite the database file itself is
+encrypted at rest. There is no equivalent for a client-server engine: the data
+lives in the server's storage, not in a file Termix owns.
+
+Concretely, on Postgres/MySQL the following are readable by anyone with database
+access, where on SQLite they were covered by the file encryption:
+
+- host names, addresses, ports and usernames
+- folder and snippet names, and **snippet contents**
+- audit log entries
+- session recording metadata and paths
+- user names, roles and API key hashes
+
+None of these are credentials — those stay encrypted — but together they
+describe your estate.
+
+**If you run Postgres or MySQL, encryption at rest is your responsibility**:
+transparent data encryption, an encrypted volume, or an encrypted filesystem.
+Termix does not provide it and cannot.
+
+### Threat model, side by side
+
+| | SQLite | Postgres / MySQL |
+| ----------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------ |
+| Stolen database file / volume | credentials encrypted, everything else encrypted | credentials encrypted, **rest depends on your storage encryption** |
+| Database access without app access | credentials unreadable | credentials unreadable |
+| Application compromise while a user is unlocked | that user's secrets readable | same |
+| Backups | inherit file encryption | **plain unless you encrypt them** |
+
+The second row is the point of field-level encryption, and it holds everywhere.
+The first and last rows are where the backends genuinely differ.
+
+## Running on Postgres or MySQL
+
+Two variables. Unset, nothing changes and SQLite is used exactly as before.
+
+```
+DATABASE_DIALECT=postgres
+DATABASE_URL=postgres://user:password@host:5432/termix
+```
+
+```
+DATABASE_DIALECT=mysql
+DATABASE_URL=mysql://user:password@host:3306/termix
+```
+
+`mariadb://` is accepted for MySQL. The scheme is checked against the dialect
+before a connection is attempted, so a mismatch fails with a readable message
+rather than a driver error deep in a stack.
+
+Point it at an **empty** database. Migrations are applied at startup, from
+`drizzle/postgres` or `drizzle/mysql`, and drizzle records what it has applied —
+so several instances against one database are safe, and so is restarting.
+
+There is no migration path from an existing SQLite database. Exporting one and
+importing it into Postgres is not something this branch does.
+
+### Docker
+
+`drizzle/` ships in the image. A compose service needs only the two variables:
+
+Added to the compose file in the README, that is one service and two variables:
+
+```yaml
+services:
+ termix:
+ image: ghcr.io/lukegus/termix:latest
+ environment:
+ PORT: "8080"
+ DATABASE_DIALECT: postgres
+ DATABASE_URL: postgres://termix:termix@db:5432/termix
+ depends_on:
+ - db
+
+ db:
+ image: postgres:16
+ restart: unless-stopped
+ environment:
+ POSTGRES_USER: termix
+ POSTGRES_PASSWORD: termix
+ POSTGRES_DB: termix
+ volumes:
+ - pgdata:/var/lib/postgresql/data
+
+volumes:
+ pgdata:
+```
+
+`DATA_DIR` is still used for uploads and recordings on every backend. Only the
+database itself moves.
+
+## What is verified, and how
+
+`npm run verify:dialect -- ` applies the migrations to an empty database and
+drives the real repository classes against it, asserting values rather than the
+absence of exceptions.
+
+The repository test suite also runs against each engine:
+
+```
+TEST_DIALECT=postgres TEST_DATABASE_URL= npx vitest run \
+ src/backend/tests/database/repositories --no-file-parallelism
+```
+
+CI runs both, against PostgreSQL 16 and MySQL 8 service containers. Eighteen
+tests assert on bytes stored by the SQLite driver and skip on other engines;
+they still run in the SQLite pass.
+
+Tested against PostgreSQL 16 and MySQL 8. **MariaDB is not a substitute for
+MySQL when testing** — it accepts DDL that MySQL 8 rejects, which has hidden a
+real defect here more than once.
+
+### What neither of them covers
+
+Both harnesses build a `DatabaseContext` of their own, so neither runs
+`createCurrentRepositoryContext()` — the one the application actually uses.
+That gap hid a hardcoded `dialect: "sqlite"` in it: every engine reported
+itself as SQLite at runtime while all three test passes stayed green, which on
+MySQL meant `upsert` reached for `onConflictDoUpdate` and died with a
+TypeError on the first write.
+
+Anything the factory decides from the dialect needs its own test against the
+factory. Asserting it through a hand-built context proves nothing about what
+runs in production.
+
+## Known limits
+
+- The desktop app always uses SQLite. It embeds its own backend and cannot ship
+ a database server.
+- Repositories import the SQLite table definitions on every engine. That is
+ correct — the query builder needs identifiers and value encoders, and those
+ agree — but it means `PortableDatabase` is a named approximation rather than a
+ guarantee. See `repositories/database-context.ts`.
+- `getCurrentSettingValue` is a synchronous read. On Postgres and MySQL it comes
+ from a cache primed at startup and kept current by `SettingsRepository`,
+ because those drivers have no synchronous query.
+
+ That cache is per-process, so on a **multi-replica** deployment a setting
+ changed on one instance does not reach the others through the write path. Each
+ replica re-reads the settings table every 30 seconds
+ (`SETTINGS_CACHE_REFRESH_SECONDS`, 0 to disable), which does not make settings
+ immediately consistent — it bounds how long they can disagree. Changing a
+ setting takes effect on the replica that made the change at once, and on the
+ others within the interval.
+
+- **Importing a backup is SQLite-only.** The restore writes tables in an order
+ that is not dependency-safe and relies on `PRAGMA foreign_keys = OFF`, which
+ has no equivalent here: Postgres needs superuser to disable triggers, and
+ MySQL's session-scoped switch is not guaranteed across a pool. It refuses with
+ a message rather than failing partway through and leaving a half-restored
+ database. Restore into Postgres or MySQL with their own tooling.
+- **`LIKE` is case-insensitive on SQLite and case-sensitive on Postgres.** The
+ four places that use it match folder path prefixes and settings keys, so the
+ practical effect is that renaming a folder `prod` on SQLite also catches
+ `PROD / api` and on Postgres does not. Postgres is arguably the more correct
+ of the two; nothing was changed to make them agree, because that would alter
+ SQLite behaviour for existing deployments.
+- The SQLite-era data migrations — legacy shared-credential cleanup, the
+ shared-host-secrets rebuild, per-user field-encryption backfill — do not run on
+ the other engines. A database created by the drizzle migrations never had the
+ shapes they repair.
diff --git a/drizzle.config.mysql.ts b/drizzle.config.mysql.ts
new file mode 100644
index 00000000..5665bf7b
--- /dev/null
+++ b/drizzle.config.mysql.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "drizzle-kit";
+
+export default defineConfig({
+ dialect: "mysql",
+ schema: "./src/backend/database/db/schema.mysql.ts",
+ out: "./drizzle/mysql",
+});
diff --git a/drizzle.config.pg.ts b/drizzle.config.pg.ts
new file mode 100644
index 00000000..91292349
--- /dev/null
+++ b/drizzle.config.pg.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "drizzle-kit";
+
+export default defineConfig({
+ dialect: "postgresql",
+ schema: "./src/backend/database/db/schema.pg.ts",
+ out: "./drizzle/postgres",
+});
diff --git a/drizzle.config.sqlite.ts b/drizzle.config.sqlite.ts
new file mode 100644
index 00000000..7941897c
--- /dev/null
+++ b/drizzle.config.sqlite.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "drizzle-kit";
+
+export default defineConfig({
+ dialect: "sqlite",
+ schema: "./src/backend/database/db/schema.ts",
+ out: "./drizzle/sqlite",
+});
diff --git a/drizzle/mysql/0000_clean_pretty_boy.sql b/drizzle/mysql/0000_clean_pretty_boy.sql
new file mode 100644
index 00000000..d138297a
--- /dev/null
+++ b/drizzle/mysql/0000_clean_pretty_boy.sql
@@ -0,0 +1,890 @@
+CREATE TABLE `alert_firings` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `rule_id` int NOT NULL,
+ `host_id` int NOT NULL,
+ `host_name` text NOT NULL,
+ `fired_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `resolved_at` text,
+ `value` double,
+ `message` text NOT NULL,
+ `severity` text NOT NULL DEFAULT ('warning'),
+ `acknowledged` boolean NOT NULL DEFAULT false,
+ CONSTRAINT `alert_firings_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `alert_rule_channels` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `rule_id` int NOT NULL,
+ `channel_id` int NOT NULL,
+ CONSTRAINT `alert_rule_channels_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `alert_rules` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int,
+ `name` varchar(255) NOT NULL,
+ `enabled` boolean NOT NULL DEFAULT true,
+ `trigger_type` text NOT NULL,
+ `threshold_value` double,
+ `threshold_duration_seconds` int,
+ `cooldown_minutes` int NOT NULL DEFAULT 15,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `alert_rules_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `api_keys` (
+ `id` varchar(255) NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `token_hash` text NOT NULL,
+ `token_prefix` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text,
+ `last_used_at` text,
+ `is_active` boolean NOT NULL DEFAULT true,
+ CONSTRAINT `api_keys_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `audit_logs` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255),
+ `username` text NOT NULL,
+ `action` text NOT NULL,
+ `resource_type` text NOT NULL,
+ `resource_id` text,
+ `resource_name` text,
+ `details` text,
+ `ip_address` text,
+ `user_agent` text,
+ `success` boolean NOT NULL,
+ `error_message` text,
+ `timestamp` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `audit_logs_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `c2s_tunnel_presets` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `config` text NOT NULL,
+ `platform` text,
+ `computer_name` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `c2s_tunnel_presets_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `command_history` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `command` text NOT NULL,
+ `executed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `command_history_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `dashboard_service_links` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `label` text NOT NULL,
+ `url` text NOT NULL,
+ `order` int NOT NULL DEFAULT 0,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `dashboard_service_links_id` PRIMARY KEY(`id`),
+ CONSTRAINT `dashboard_service_links_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `dismissed_alerts` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `alert_id` text NOT NULL,
+ `dismissed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `dismissed_alerts_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_pinned` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `path` text NOT NULL,
+ `pinned_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `file_manager_pinned_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_recent` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `path` text NOT NULL,
+ `last_opened` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `file_manager_recent_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_shortcuts` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `path` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `file_manager_shortcuts_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `homepage_items` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `type_id` text NOT NULL,
+ `title` text,
+ `config` text NOT NULL DEFAULT ('{}'),
+ `folder_id` int,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `homepage_items_id` PRIMARY KEY(`id`),
+ CONSTRAINT `homepage_items_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `homepage_layouts` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `layout` text NOT NULL DEFAULT ('{}'),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `homepage_layouts_id` PRIMARY KEY(`id`),
+ CONSTRAINT `homepage_layouts_user_id_unique` UNIQUE(`user_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `host_access` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `host_id` int NOT NULL,
+ `user_id` varchar(255),
+ `role_id` int,
+ `granted_by` varchar(255) NOT NULL,
+ `permission_level` text NOT NULL DEFAULT ('connect'),
+ `expires_at` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `last_accessed_at` text,
+ `access_count` int NOT NULL DEFAULT 0,
+ CONSTRAINT `host_access_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `host_health_checks` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `checks` text NOT NULL,
+ `interval_seconds` int NOT NULL DEFAULT 300,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `host_health_checks_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_host_health_checks_user_host` UNIQUE(`user_id`,`host_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `host_health_history` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `check_id` text NOT NULL,
+ `ts` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `ok` boolean NOT NULL,
+ `latency_ms` int,
+ `detail` text,
+ CONSTRAINT `host_health_history_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `host_metrics_history` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `host_id` int NOT NULL,
+ `ts` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `cpu_percent` double,
+ `mem_percent` double,
+ `disk_percent` double,
+ `net_rx_bytes` int,
+ `net_tx_bytes` int,
+ CONSTRAINT `host_metrics_history_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `host_metrics_preferences` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `layout` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `host_metrics_preferences_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_host_metrics_prefs_user_host` UNIQUE(`user_id`,`host_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `ssh_data` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `connection_type` text NOT NULL DEFAULT ('ssh'),
+ `name` varchar(255),
+ `ip` text NOT NULL,
+ `port` int NOT NULL,
+ `username` text NOT NULL,
+ `folder` text,
+ `tags` text,
+ `pin` boolean NOT NULL DEFAULT false,
+ `auth_type` text NOT NULL,
+ `use_warpgate` boolean NOT NULL DEFAULT false,
+ `share_ssh_auth` boolean NOT NULL DEFAULT false,
+ `force_keyboard_interactive` text,
+ `password` text,
+ `key` text,
+ `key_password` text,
+ `key_type` text,
+ `sudo_password` text,
+ `autostart_password` text,
+ `autostart_key` text,
+ `autostart_key_password` text,
+ `credential_id` int,
+ `override_credential_username` boolean,
+ `vault_profile_id` int,
+ `enable_terminal` boolean NOT NULL DEFAULT true,
+ `enable_session_logging` boolean NOT NULL DEFAULT true,
+ `allow_session_sharing` boolean NOT NULL DEFAULT true,
+ `enable_command_history` boolean NOT NULL DEFAULT true,
+ `enable_tunnel` boolean NOT NULL DEFAULT true,
+ `tunnel_connections` text,
+ `jump_hosts` text,
+ `enable_file_manager` boolean NOT NULL DEFAULT true,
+ `scp_legacy` boolean NOT NULL DEFAULT false,
+ `enable_docker` boolean NOT NULL DEFAULT false,
+ `enable_tmux_monitor` boolean NOT NULL DEFAULT false,
+ `show_terminal_in_sidebar` boolean NOT NULL DEFAULT true,
+ `show_file_manager_in_sidebar` boolean NOT NULL DEFAULT false,
+ `show_tunnel_in_sidebar` boolean NOT NULL DEFAULT false,
+ `show_docker_in_sidebar` boolean NOT NULL DEFAULT false,
+ `show_server_stats_in_sidebar` boolean NOT NULL DEFAULT false,
+ `default_path` text,
+ `stats_config` text,
+ `docker_config` text,
+ `enable_proxmox` boolean NOT NULL DEFAULT false,
+ `proxmox_config` text,
+ `terminal_config` text,
+ `quick_actions` text,
+ `notes` text,
+ `enable_ssh` boolean NOT NULL DEFAULT true,
+ `enable_rdp` boolean NOT NULL DEFAULT false,
+ `enable_vnc` boolean NOT NULL DEFAULT false,
+ `enable_telnet` boolean NOT NULL DEFAULT false,
+ `ssh_port` int DEFAULT 22,
+ `rdp_port` int DEFAULT 3389,
+ `vnc_port` int DEFAULT 5900,
+ `telnet_port` int DEFAULT 23,
+ `rdp_credential_id` int,
+ `rdp_user` text,
+ `rdp_password` text,
+ `rdp_domain` text,
+ `rdp_security` text,
+ `rdp_ignore_cert` boolean DEFAULT false,
+ `vnc_credential_id` int,
+ `vnc_password` text,
+ `vnc_user` text,
+ `telnet_user` text,
+ `telnet_password` text,
+ `telnet_credential_id` int,
+ `rdp_auth_type` text,
+ `vnc_auth_type` text,
+ `telnet_auth_type` text,
+ `domain` text,
+ `security` text,
+ `ignore_cert` boolean DEFAULT false,
+ `guacamole_config` text,
+ `use_socks5` boolean,
+ `socks5_host` text,
+ `socks5_port` int,
+ `socks5_username` text,
+ `socks5_password` text,
+ `socks5_proxy_chain` text,
+ `connection_origin` text,
+ `mac_address` text,
+ `wol_broadcast_address` text,
+ `port_knock_sequence` text,
+ `host_key_fingerprint` text,
+ `host_key_type` text,
+ `host_key_algorithm` text DEFAULT ('sha256'),
+ `host_key_first_seen` text,
+ `host_key_last_verified` text,
+ `host_key_changed_count` int DEFAULT 0,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `ssh_data_id` PRIMARY KEY(`id`),
+ CONSTRAINT `ssh_data_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `network_topology` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `topology` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `network_topology_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `notification_channels` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `type` text NOT NULL,
+ `config` text NOT NULL,
+ `enabled` boolean NOT NULL DEFAULT true,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `notification_channels_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `opkssh_tokens` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `ssh_cert` text NOT NULL,
+ `private_key` text NOT NULL,
+ `email` text,
+ `sub` text,
+ `issuer` text,
+ `audience` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text NOT NULL,
+ `last_used` text,
+ CONSTRAINT `opkssh_tokens_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_opkssh_tokens_user_host` UNIQUE(`user_id`,`host_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `recent_activity` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `type` text NOT NULL,
+ `host_id` int NOT NULL,
+ `host_name` text,
+ `timestamp` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `recent_activity_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `roles` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `display_name` text NOT NULL,
+ `description` text,
+ `is_system` boolean NOT NULL DEFAULT false,
+ `permissions` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `roles_id` PRIMARY KEY(`id`),
+ CONSTRAINT `roles_name_unique` UNIQUE(`name`)
+);
+--> statement-breakpoint
+CREATE TABLE `session_recordings` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `host_id` int NOT NULL,
+ `user_id` varchar(255),
+ `username` text,
+ `access_id` int,
+ `started_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `ended_at` text,
+ `duration` int,
+ `commands` text,
+ `dangerous_actions` text,
+ `recording_path` text,
+ `protocol` varchar(255) NOT NULL DEFAULT 'ssh',
+ `format` text NOT NULL DEFAULT ('text'),
+ `terminated_by_owner` boolean DEFAULT false,
+ `termination_reason` text,
+ CONSTRAINT `session_recordings_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `session_share_participants` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `share_id` varchar(255) NOT NULL,
+ `user_id` varchar(255),
+ `guest_label` text,
+ `joined_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `left_at` text,
+ CONSTRAINT `session_share_participants_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `session_shares` (
+ `id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `owner_user_id` varchar(255) NOT NULL,
+ `protocol` varchar(255) NOT NULL,
+ `session_id` text NOT NULL,
+ `tab_instance_id` text,
+ `share_type` text NOT NULL,
+ `target_user_id` varchar(255),
+ `link_token` varchar(255),
+ `permission_level` text NOT NULL DEFAULT ('read-only'),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text NOT NULL,
+ `revoked_at` text,
+ `last_joined_at` text,
+ `join_count` int NOT NULL DEFAULT 0,
+ CONSTRAINT `session_shares_id` PRIMARY KEY(`id`),
+ CONSTRAINT `session_shares_link_token_unique` UNIQUE(`link_token`)
+);
+--> statement-breakpoint
+CREATE TABLE `sessions` (
+ `id` varchar(255) NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `jwt_token` text NOT NULL,
+ `device_type` text NOT NULL,
+ `device_info` text NOT NULL,
+ `oidc_sub` text,
+ `oidc_sid` text,
+ `sso_provider_id` int,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text NOT NULL,
+ `last_active_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `sessions_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `settings` (
+ `key` varchar(255) NOT NULL,
+ `value` text NOT NULL,
+ CONSTRAINT `settings_key` PRIMARY KEY(`key`)
+);
+--> statement-breakpoint
+CREATE TABLE `shared_host_auth_overrides` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `host_id` int NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `protocol` varchar(255) NOT NULL DEFAULT 'ssh',
+ `credential_id` int NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `shared_host_auth_overrides_id` PRIMARY KEY(`id`),
+ CONSTRAINT `shared_host_auth_overrides_host_user_protocol_unique` UNIQUE(`host_id`,`user_id`,`protocol`)
+);
+--> statement-breakpoint
+CREATE TABLE `shared_host_secrets` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `host_access_id` int NOT NULL,
+ `target_user_id` varchar(255) NOT NULL,
+ `protocol` varchar(255) NOT NULL DEFAULT 'ssh',
+ `source_type` text NOT NULL DEFAULT ('credential'),
+ `original_credential_id` int,
+ `encrypted_username` text,
+ `encrypted_auth_type` text,
+ `encrypted_password` text,
+ `encrypted_key` text,
+ `encrypted_key_password` text,
+ `encrypted_key_type` text,
+ `encrypted_domain` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `shared_host_secrets_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_shared_host_secrets_scope` UNIQUE(`host_access_id`,`target_user_id`,`protocol`)
+);
+--> statement-breakpoint
+CREATE TABLE `snippet_access` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `snippet_id` int NOT NULL,
+ `user_id` varchar(255),
+ `role_id` int,
+ `granted_by` varchar(255) NOT NULL,
+ `permission_level` text NOT NULL DEFAULT ('view'),
+ `expires_at` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `snippet_access_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `snippet_folders` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `color` text,
+ `icon` text,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `snippet_folders_id` PRIMARY KEY(`id`),
+ CONSTRAINT `snippet_folders_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `snippets` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `content` text NOT NULL,
+ `description` text,
+ `folder` text,
+ `order` int NOT NULL DEFAULT 0,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `host_filter` text,
+ CONSTRAINT `snippets_id` PRIMARY KEY(`id`),
+ CONSTRAINT `snippets_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `ssh_credential_usage` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `credential_id` int NOT NULL,
+ `host_id` int NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `used_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `ssh_credential_usage_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `ssh_credentials` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` text,
+ `folder` text,
+ `tags` text,
+ `auth_type` text NOT NULL,
+ `username` text,
+ `password` text,
+ `key` text,
+ `private_key` text,
+ `public_key` text,
+ `key_password` text,
+ `key_type` text,
+ `detected_key_type` text,
+ `cert_public_key` text,
+ `usage_count` int NOT NULL DEFAULT 0,
+ `last_used` text,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `ssh_credentials_id` PRIMARY KEY(`id`),
+ CONSTRAINT `ssh_credentials_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `ssh_folders` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `color` text,
+ `icon` text,
+ `credential_id` int,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `ssh_folders_id` PRIMARY KEY(`id`),
+ CONSTRAINT `ssh_folders_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `sso_providers` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `type` text NOT NULL,
+ `enabled` boolean NOT NULL DEFAULT true,
+ `display_order` int NOT NULL DEFAULT 0,
+ `config` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `sso_providers_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `sync_tombstones` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `entity_type` text NOT NULL,
+ `sync_id` varchar(255) NOT NULL,
+ `deleted_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `sync_tombstones_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `termix_identities` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `description` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `termix_identities_id` PRIMARY KEY(`id`),
+ CONSTRAINT `termix_identities_user_id_unique` UNIQUE(`user_id`),
+ CONSTRAINT `termix_identities_handle_unique` UNIQUE(`handle`)
+);
+--> statement-breakpoint
+CREATE TABLE `termix_identity_ca` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `identity_id` int NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `public_key` text NOT NULL,
+ `private_key` text NOT NULL,
+ `validity_days` int NOT NULL DEFAULT 90,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `termix_identity_ca_id` PRIMARY KEY(`id`),
+ CONSTRAINT `termix_identity_ca_identity_id_unique` UNIQUE(`identity_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `termix_identity_keys` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `identity_id` int NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `public_key` text NOT NULL,
+ `key_type` text NOT NULL,
+ `algorithm` text NOT NULL,
+ `label` text,
+ `comment` text,
+ `source` text NOT NULL DEFAULT ('manual'),
+ `credential_id` int,
+ `enabled` boolean NOT NULL DEFAULT true,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `termix_identity_keys_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `tmux_session_tags` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `host_id` int NOT NULL,
+ `session_name` text NOT NULL,
+ `tag` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `tmux_session_tags_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `transfer_recent` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `source_host_id` int NOT NULL,
+ `dest_host_id` int NOT NULL,
+ `dest_path` text NOT NULL,
+ `dest_path_label` text NOT NULL,
+ `last_used` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `transfer_recent_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `trusted_devices` (
+ `id` varchar(255) NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `device_fingerprint` text NOT NULL,
+ `device_type` text NOT NULL,
+ `device_info` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text NOT NULL,
+ `last_used_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `trusted_devices_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `user_open_tabs` (
+ `id` varchar(255) NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `tab_type` text NOT NULL,
+ `host_id` int,
+ `label` text NOT NULL,
+ `tab_order` int NOT NULL DEFAULT 0,
+ `backend_session_id` text,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `user_open_tabs_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `user_preferences` (
+ `user_id` varchar(255) NOT NULL,
+ `reopen_tabs_on_login` boolean NOT NULL DEFAULT false,
+ `theme` text,
+ `font_size` text,
+ `accent_color` text,
+ `language` text,
+ `storage_mode` text,
+ `command_autocomplete` boolean,
+ `command_palette_enabled` boolean,
+ `show_host_tags` boolean,
+ `host_tray_on_click` boolean,
+ `pin_app_rail` boolean,
+ `expand_app_rail_on_hover` boolean,
+ `folders_collapsed` boolean,
+ `confirm_snippet_execution` boolean,
+ `disable_update_check` boolean,
+ `confirm_tab_close` boolean,
+ `hidden_rail_tabs` text,
+ `compact_host_view` boolean,
+ `status_color_scheme` text,
+ `custom_themes` text,
+ `custom_keybindings` text,
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `user_preferences_user_id` PRIMARY KEY(`user_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `user_roles` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `role_id` int NOT NULL,
+ `granted_by` varchar(255),
+ `granted_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `user_roles_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_user_roles_user_role` UNIQUE(`user_id`,`role_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `users` (
+ `id` varchar(255) NOT NULL,
+ `username` text NOT NULL,
+ `password_hash` text NOT NULL,
+ `is_admin` boolean NOT NULL DEFAULT false,
+ `is_oidc` boolean NOT NULL DEFAULT false,
+ `oidc_identifier` text,
+ `sso_provider_id` int,
+ `client_id` text,
+ `client_secret` text,
+ `issuer_url` text,
+ `authorization_url` text,
+ `token_url` text,
+ `identifier_path` text,
+ `name_path` text,
+ `scopes` text DEFAULT ('openid email profile'),
+ `totp_secret` text,
+ `totp_enabled` boolean NOT NULL DEFAULT false,
+ `totp_backup_codes` text,
+ `registered_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `donation_modal_dismissed` boolean NOT NULL DEFAULT false,
+ CONSTRAINT `users_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `vault_profiles` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` text,
+ `folder` text,
+ `tags` text,
+ `vault_addr` text NOT NULL,
+ `vault_namespace` text,
+ `oidc_mount` text,
+ `oidc_role` text,
+ `ssh_mount` text,
+ `ssh_role` text NOT NULL,
+ `valid_principals` text,
+ `key_type` text,
+ `shared` boolean NOT NULL DEFAULT false,
+ `sync_id` varchar(255),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ CONSTRAINT `vault_profiles_id` PRIMARY KEY(`id`),
+ CONSTRAINT `vault_profiles_sync_id_unique` UNIQUE(`sync_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `vault_tokens` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `profile_id` int NOT NULL,
+ `ssh_cert` text NOT NULL,
+ `private_key` text NOT NULL,
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `expires_at` text NOT NULL,
+ `last_used` text,
+ CONSTRAINT `vault_tokens_id` PRIMARY KEY(`id`),
+ CONSTRAINT `idx_vault_tokens_user_profile` UNIQUE(`user_id`,`profile_id`)
+);
+--> statement-breakpoint
+CREATE TABLE `webauthn_credentials` (
+ `id` varchar(255) NOT NULL,
+ `user_id` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `credential_id` text NOT NULL,
+ `public_key` text NOT NULL,
+ `counter` int NOT NULL DEFAULT 0,
+ `device_type` text,
+ `backed_up` boolean NOT NULL DEFAULT false,
+ `transports` text,
+ `user_verification` text NOT NULL DEFAULT ('preferred'),
+ `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
+ `last_used_at` text,
+ CONSTRAINT `webauthn_credentials_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+ALTER TABLE `alert_firings` ADD CONSTRAINT `alert_firings_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `alert_firings` ADD CONSTRAINT `alert_firings_rule_id_alert_rules_id_fk` FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `alert_rule_channels` ADD CONSTRAINT `alert_rule_channels_rule_id_alert_rules_id_fk` FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `alert_rule_channels` ADD CONSTRAINT `alert_rule_channels_channel_id_notification_channels_id_fk` FOREIGN KEY (`channel_id`) REFERENCES `notification_channels`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `alert_rules` ADD CONSTRAINT `alert_rules_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `alert_rules` ADD CONSTRAINT `alert_rules_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `api_keys` ADD CONSTRAINT `api_keys_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `audit_logs` ADD CONSTRAINT `audit_logs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `c2s_tunnel_presets` ADD CONSTRAINT `c2s_tunnel_presets_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `command_history` ADD CONSTRAINT `command_history_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `command_history` ADD CONSTRAINT `command_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `dashboard_service_links` ADD CONSTRAINT `dashboard_service_links_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `dismissed_alerts` ADD CONSTRAINT `dismissed_alerts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_pinned` ADD CONSTRAINT `file_manager_pinned_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_pinned` ADD CONSTRAINT `file_manager_pinned_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_recent` ADD CONSTRAINT `file_manager_recent_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_recent` ADD CONSTRAINT `file_manager_recent_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_shortcuts` ADD CONSTRAINT `file_manager_shortcuts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `file_manager_shortcuts` ADD CONSTRAINT `file_manager_shortcuts_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `homepage_items` ADD CONSTRAINT `homepage_items_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `homepage_layouts` ADD CONSTRAINT `homepage_layouts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_access` ADD CONSTRAINT `host_access_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_access` ADD CONSTRAINT `host_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_access` ADD CONSTRAINT `host_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_access` ADD CONSTRAINT `host_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_health_checks` ADD CONSTRAINT `host_health_checks_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_health_checks` ADD CONSTRAINT `host_health_checks_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_health_history` ADD CONSTRAINT `host_health_history_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_health_history` ADD CONSTRAINT `host_health_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_metrics_history` ADD CONSTRAINT `host_metrics_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_metrics_preferences` ADD CONSTRAINT `host_metrics_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `host_metrics_preferences` ADD CONSTRAINT `host_metrics_preferences_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_vault_profile_id_vault_profiles_id_fk` FOREIGN KEY (`vault_profile_id`) REFERENCES `vault_profiles`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_rdp_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`rdp_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_vnc_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`vnc_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_telnet_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`telnet_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `network_topology` ADD CONSTRAINT `network_topology_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `notification_channels` ADD CONSTRAINT `notification_channels_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `opkssh_tokens` ADD CONSTRAINT `opkssh_tokens_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `opkssh_tokens` ADD CONSTRAINT `opkssh_tokens_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `recent_activity` ADD CONSTRAINT `recent_activity_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `recent_activity` ADD CONSTRAINT `recent_activity_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_access_id_host_access_id_fk` FOREIGN KEY (`access_id`) REFERENCES `host_access`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_share_participants` ADD CONSTRAINT `session_share_participants_share_id_session_shares_id_fk` FOREIGN KEY (`share_id`) REFERENCES `session_shares`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_share_participants` ADD CONSTRAINT `session_share_participants_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_owner_user_id_users_id_fk` FOREIGN KEY (`owner_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_target_user_id_users_id_fk` FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `sessions` ADD CONSTRAINT `sessions_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_host_access_id_host_access_id_fk` FOREIGN KEY (`host_access_id`) REFERENCES `host_access`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_target_user_id_users_id_fk` FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_original_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`original_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_snippet_id_snippets_id_fk` FOREIGN KEY (`snippet_id`) REFERENCES `snippets`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippet_folders` ADD CONSTRAINT `snippet_folders_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `snippets` ADD CONSTRAINT `snippets_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_credentials` ADD CONSTRAINT `ssh_credentials_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_folders` ADD CONSTRAINT `ssh_folders_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `ssh_folders` ADD CONSTRAINT `ssh_folders_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `sync_tombstones` ADD CONSTRAINT `sync_tombstones_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identities` ADD CONSTRAINT `termix_identities_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identity_ca` ADD CONSTRAINT `termix_identity_ca_identity_id_termix_identities_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identity_ca` ADD CONSTRAINT `termix_identity_ca_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_identity_id_termix_identities_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `tmux_session_tags` ADD CONSTRAINT `tmux_session_tags_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `tmux_session_tags` ADD CONSTRAINT `tmux_session_tags_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_source_host_id_ssh_data_id_fk` FOREIGN KEY (`source_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_dest_host_id_ssh_data_id_fk` FOREIGN KEY (`dest_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `trusted_devices` ADD CONSTRAINT `trusted_devices_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_open_tabs` ADD CONSTRAINT `user_open_tabs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_open_tabs` ADD CONSTRAINT `user_open_tabs_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_preferences` ADD CONSTRAINT `user_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `vault_profiles` ADD CONSTRAINT `vault_profiles_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `vault_tokens` ADD CONSTRAINT `vault_tokens_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `vault_tokens` ADD CONSTRAINT `vault_tokens_profile_id_vault_profiles_id_fk` FOREIGN KEY (`profile_id`) REFERENCES `vault_profiles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `webauthn_credentials` ADD CONSTRAINT `webauthn_credentials_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/drizzle/mysql/meta/0000_snapshot.json b/drizzle/mysql/meta/0000_snapshot.json
new file mode 100644
index 00000000..4c6f31cf
--- /dev/null
+++ b/drizzle/mysql/meta/0000_snapshot.json
@@ -0,0 +1,6437 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "2b238de4-3ad7-4b57-8c58-308d6da06c02",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "tables": {
+ "alert_firings": {
+ "name": "alert_firings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fired_at": {
+ "name": "fired_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "double",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "severity": {
+ "name": "severity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('warning')"
+ },
+ "acknowledged": {
+ "name": "acknowledged",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_firings_user_id_users_id_fk": {
+ "name": "alert_firings_user_id_users_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_firings_rule_id_alert_rules_id_fk": {
+ "name": "alert_firings_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "alert_firings_id": {
+ "name": "alert_firings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "alert_rule_channels": {
+ "name": "alert_rule_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rule_channels_rule_id_alert_rules_id_fk": {
+ "name": "alert_rule_channels_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rule_channels_channel_id_notification_channels_id_fk": {
+ "name": "alert_rule_channels_channel_id_notification_channels_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "alert_rule_channels_id": {
+ "name": "alert_rule_channels_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "alert_rules": {
+ "name": "alert_rules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "threshold_value": {
+ "name": "threshold_value",
+ "type": "double",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "threshold_duration_seconds": {
+ "name": "threshold_duration_seconds",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cooldown_minutes": {
+ "name": "cooldown_minutes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 15
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rules_user_id_users_id_fk": {
+ "name": "alert_rules_user_id_users_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rules_host_id_ssh_data_id_fk": {
+ "name": "alert_rules_host_id_ssh_data_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "alert_rules_id": {
+ "name": "alert_rules_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "api_keys": {
+ "name": "api_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_keys_user_id_users_id_fk": {
+ "name": "api_keys_user_id_users_id_fk",
+ "tableFrom": "api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "api_keys_id": {
+ "name": "api_keys_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "success": {
+ "name": "success",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "audit_logs_id": {
+ "name": "audit_logs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "c2s_tunnel_presets": {
+ "name": "c2s_tunnel_presets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "computer_name": {
+ "name": "computer_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "c2s_tunnel_presets_user_id_users_id_fk": {
+ "name": "c2s_tunnel_presets_user_id_users_id_fk",
+ "tableFrom": "c2s_tunnel_presets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "c2s_tunnel_presets_id": {
+ "name": "c2s_tunnel_presets_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "command_history": {
+ "name": "command_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "executed_at": {
+ "name": "executed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "command_history_user_id_users_id_fk": {
+ "name": "command_history_user_id_users_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "command_history_host_id_ssh_data_id_fk": {
+ "name": "command_history_host_id_ssh_data_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "command_history_id": {
+ "name": "command_history_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "dashboard_service_links": {
+ "name": "dashboard_service_links",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dashboard_service_links_user_id_users_id_fk": {
+ "name": "dashboard_service_links_user_id_users_id_fk",
+ "tableFrom": "dashboard_service_links",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "dashboard_service_links_id": {
+ "name": "dashboard_service_links_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "dashboard_service_links_sync_id_unique": {
+ "name": "dashboard_service_links_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "dismissed_alerts": {
+ "name": "dismissed_alerts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "alert_id": {
+ "name": "alert_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dismissed_at": {
+ "name": "dismissed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dismissed_alerts_user_id_users_id_fk": {
+ "name": "dismissed_alerts_user_id_users_id_fk",
+ "tableFrom": "dismissed_alerts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "dismissed_alerts_id": {
+ "name": "dismissed_alerts_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "file_manager_pinned": {
+ "name": "file_manager_pinned",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_pinned_user_id_users_id_fk": {
+ "name": "file_manager_pinned_user_id_users_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_pinned_host_id_ssh_data_id_fk": {
+ "name": "file_manager_pinned_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "file_manager_pinned_id": {
+ "name": "file_manager_pinned_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "file_manager_recent": {
+ "name": "file_manager_recent",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_opened": {
+ "name": "last_opened",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_recent_user_id_users_id_fk": {
+ "name": "file_manager_recent_user_id_users_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_recent_host_id_ssh_data_id_fk": {
+ "name": "file_manager_recent_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "file_manager_recent_id": {
+ "name": "file_manager_recent_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "file_manager_shortcuts": {
+ "name": "file_manager_shortcuts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_shortcuts_user_id_users_id_fk": {
+ "name": "file_manager_shortcuts_user_id_users_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_shortcuts_host_id_ssh_data_id_fk": {
+ "name": "file_manager_shortcuts_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "file_manager_shortcuts_id": {
+ "name": "file_manager_shortcuts_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "homepage_items": {
+ "name": "homepage_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type_id": {
+ "name": "type_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('{}')"
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "homepage_items_user_id_users_id_fk": {
+ "name": "homepage_items_user_id_users_id_fk",
+ "tableFrom": "homepage_items",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "homepage_items_id": {
+ "name": "homepage_items_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "homepage_items_sync_id_unique": {
+ "name": "homepage_items_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "homepage_layouts": {
+ "name": "homepage_layouts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('{}')"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "homepage_layouts_user_id_users_id_fk": {
+ "name": "homepage_layouts_user_id_users_id_fk",
+ "tableFrom": "homepage_layouts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "homepage_layouts_id": {
+ "name": "homepage_layouts_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "homepage_layouts_user_id_unique": {
+ "name": "homepage_layouts_user_id_unique",
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "host_access": {
+ "name": "host_access",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('connect')"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "last_accessed_at": {
+ "name": "last_accessed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_count": {
+ "name": "access_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_access_host_id_ssh_data_id_fk": {
+ "name": "host_access_host_id_ssh_data_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_user_id_users_id_fk": {
+ "name": "host_access_user_id_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_role_id_roles_id_fk": {
+ "name": "host_access_role_id_roles_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_granted_by_users_id_fk": {
+ "name": "host_access_granted_by_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "host_access_id": {
+ "name": "host_access_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "host_health_checks": {
+ "name": "host_health_checks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "checks": {
+ "name": "checks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 300
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {
+ "idx_host_health_checks_user_host": {
+ "name": "idx_host_health_checks_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "host_health_checks_user_id_users_id_fk": {
+ "name": "host_health_checks_user_id_users_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_checks_host_id_ssh_data_id_fk": {
+ "name": "host_health_checks_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "host_health_checks_id": {
+ "name": "host_health_checks_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "host_health_history": {
+ "name": "host_health_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "check_id": {
+ "name": "check_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "ok": {
+ "name": "ok",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_health_history_user_id_users_id_fk": {
+ "name": "host_health_history_user_id_users_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_history_host_id_ssh_data_id_fk": {
+ "name": "host_health_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "host_health_history_id": {
+ "name": "host_health_history_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "host_metrics_history": {
+ "name": "host_metrics_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "mem_percent": {
+ "name": "mem_percent",
+ "type": "double",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "disk_percent": {
+ "name": "disk_percent",
+ "type": "double",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "net_rx_bytes": {
+ "name": "net_rx_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "net_tx_bytes": {
+ "name": "net_tx_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_metrics_history_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "host_metrics_history_id": {
+ "name": "host_metrics_history_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "host_metrics_preferences": {
+ "name": "host_metrics_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {
+ "idx_host_metrics_prefs_user_host": {
+ "name": "idx_host_metrics_prefs_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_preferences_user_id_users_id_fk": {
+ "name": "host_metrics_preferences_user_id_users_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_metrics_preferences_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_preferences_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "host_metrics_preferences_id": {
+ "name": "host_metrics_preferences_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "ssh_data": {
+ "name": "ssh_data",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('ssh')"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip": {
+ "name": "ip",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pin": {
+ "name": "pin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "use_warpgate": {
+ "name": "use_warpgate",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "share_ssh_auth": {
+ "name": "share_ssh_auth",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "force_keyboard_interactive": {
+ "name": "force_keyboard_interactive",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sudo_password": {
+ "name": "sudo_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_password": {
+ "name": "autostart_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_key": {
+ "name": "autostart_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_key_password": {
+ "name": "autostart_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "override_credential_username": {
+ "name": "override_credential_username",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vault_profile_id": {
+ "name": "vault_profile_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_terminal": {
+ "name": "enable_terminal",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_session_logging": {
+ "name": "enable_session_logging",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "allow_session_sharing": {
+ "name": "allow_session_sharing",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_command_history": {
+ "name": "enable_command_history",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_tunnel": {
+ "name": "enable_tunnel",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "tunnel_connections": {
+ "name": "tunnel_connections",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "jump_hosts": {
+ "name": "jump_hosts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_file_manager": {
+ "name": "enable_file_manager",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "scp_legacy": {
+ "name": "scp_legacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_docker": {
+ "name": "enable_docker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_tmux_monitor": {
+ "name": "enable_tmux_monitor",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_terminal_in_sidebar": {
+ "name": "show_terminal_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "show_file_manager_in_sidebar": {
+ "name": "show_file_manager_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_tunnel_in_sidebar": {
+ "name": "show_tunnel_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_docker_in_sidebar": {
+ "name": "show_docker_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_server_stats_in_sidebar": {
+ "name": "show_server_stats_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_path": {
+ "name": "default_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stats_config": {
+ "name": "stats_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "docker_config": {
+ "name": "docker_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_proxmox": {
+ "name": "enable_proxmox",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "proxmox_config": {
+ "name": "proxmox_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "terminal_config": {
+ "name": "terminal_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "quick_actions": {
+ "name": "quick_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_ssh": {
+ "name": "enable_ssh",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_rdp": {
+ "name": "enable_rdp",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_vnc": {
+ "name": "enable_vnc",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_telnet": {
+ "name": "enable_telnet",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "ssh_port": {
+ "name": "ssh_port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 22
+ },
+ "rdp_port": {
+ "name": "rdp_port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 3389
+ },
+ "vnc_port": {
+ "name": "vnc_port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 5900
+ },
+ "telnet_port": {
+ "name": "telnet_port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 23
+ },
+ "rdp_credential_id": {
+ "name": "rdp_credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_user": {
+ "name": "rdp_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_password": {
+ "name": "rdp_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_domain": {
+ "name": "rdp_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_security": {
+ "name": "rdp_security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_ignore_cert": {
+ "name": "rdp_ignore_cert",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "vnc_credential_id": {
+ "name": "vnc_credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_password": {
+ "name": "vnc_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_user": {
+ "name": "vnc_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_user": {
+ "name": "telnet_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_password": {
+ "name": "telnet_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_credential_id": {
+ "name": "telnet_credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_auth_type": {
+ "name": "rdp_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_auth_type": {
+ "name": "vnc_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_auth_type": {
+ "name": "telnet_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "security": {
+ "name": "security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ignore_cert": {
+ "name": "ignore_cert",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "guacamole_config": {
+ "name": "guacamole_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "use_socks5": {
+ "name": "use_socks5",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_host": {
+ "name": "socks5_host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_port": {
+ "name": "socks5_port",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_username": {
+ "name": "socks5_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_password": {
+ "name": "socks5_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_proxy_chain": {
+ "name": "socks5_proxy_chain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_origin": {
+ "name": "connection_origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "mac_address": {
+ "name": "mac_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "wol_broadcast_address": {
+ "name": "wol_broadcast_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port_knock_sequence": {
+ "name": "port_knock_sequence",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_fingerprint": {
+ "name": "host_key_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_type": {
+ "name": "host_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_algorithm": {
+ "name": "host_key_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "('sha256')"
+ },
+ "host_key_first_seen": {
+ "name": "host_key_first_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_last_verified": {
+ "name": "host_key_last_verified",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_changed_count": {
+ "name": "host_key_changed_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_data_user_id_users_id_fk": {
+ "name": "ssh_data_user_id_users_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_data_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vault_profile_id_vault_profiles_id_fk": {
+ "name": "ssh_data_vault_profile_id_vault_profiles_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "vault_profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_rdp_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "rdp_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vnc_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "vnc_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_telnet_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "telnet_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "ssh_data_id": {
+ "name": "ssh_data_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "ssh_data_sync_id_unique": {
+ "name": "ssh_data_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "network_topology": {
+ "name": "network_topology",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "topology": {
+ "name": "topology",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "network_topology_user_id_users_id_fk": {
+ "name": "network_topology_user_id_users_id_fk",
+ "tableFrom": "network_topology",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "network_topology_id": {
+ "name": "network_topology_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "notification_channels": {
+ "name": "notification_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_channels_user_id_users_id_fk": {
+ "name": "notification_channels_user_id_users_id_fk",
+ "tableFrom": "notification_channels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "notification_channels_id": {
+ "name": "notification_channels_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "opkssh_tokens": {
+ "name": "opkssh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sub": {
+ "name": "sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_opkssh_tokens_user_host": {
+ "name": "idx_opkssh_tokens_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "opkssh_tokens_user_id_users_id_fk": {
+ "name": "opkssh_tokens_user_id_users_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "opkssh_tokens_host_id_ssh_data_id_fk": {
+ "name": "opkssh_tokens_host_id_ssh_data_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "opkssh_tokens_id": {
+ "name": "opkssh_tokens_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "recent_activity": {
+ "name": "recent_activity",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recent_activity_user_id_users_id_fk": {
+ "name": "recent_activity_user_id_users_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "recent_activity_host_id_ssh_data_id_fk": {
+ "name": "recent_activity_host_id_ssh_data_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "recent_activity_id": {
+ "name": "recent_activity_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "roles_id": {
+ "name": "roles_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "session_recordings": {
+ "name": "session_recordings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_id": {
+ "name": "access_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "commands": {
+ "name": "commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dangerous_actions": {
+ "name": "dangerous_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "recording_path": {
+ "name": "recording_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "format": {
+ "name": "format",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('text')"
+ },
+ "terminated_by_owner": {
+ "name": "terminated_by_owner",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "termination_reason": {
+ "name": "termination_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_recordings_host_id_ssh_data_id_fk": {
+ "name": "session_recordings_host_id_ssh_data_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_recordings_user_id_users_id_fk": {
+ "name": "session_recordings_user_id_users_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "session_recordings_access_id_host_access_id_fk": {
+ "name": "session_recordings_access_id_host_access_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "session_recordings_id": {
+ "name": "session_recordings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "session_share_participants": {
+ "name": "session_share_participants",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "share_id": {
+ "name": "share_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "guest_label": {
+ "name": "guest_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "left_at": {
+ "name": "left_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_share_participants_share_id_session_shares_id_fk": {
+ "name": "session_share_participants_share_id_session_shares_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "session_shares",
+ "columnsFrom": [
+ "share_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_share_participants_user_id_users_id_fk": {
+ "name": "session_share_participants_user_id_users_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "session_share_participants_id": {
+ "name": "session_share_participants_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "session_shares": {
+ "name": "session_shares",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_instance_id": {
+ "name": "tab_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "share_type": {
+ "name": "share_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "link_token": {
+ "name": "link_token",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('read-only')"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_joined_at": {
+ "name": "last_joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "join_count": {
+ "name": "join_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_shares_host_id_ssh_data_id_fk": {
+ "name": "session_shares_host_id_ssh_data_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_owner_user_id_users_id_fk": {
+ "name": "session_shares_owner_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_target_user_id_users_id_fk": {
+ "name": "session_shares_target_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "session_shares_id": {
+ "name": "session_shares_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "session_shares_link_token_unique": {
+ "name": "session_shares_link_token_unique",
+ "columns": [
+ "link_token"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "jwt_token": {
+ "name": "jwt_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "oidc_sub": {
+ "name": "oidc_sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_sid": {
+ "name": "oidc_sid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_active_at": {
+ "name": "last_active_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sessions_id": {
+ "name": "sessions_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "settings_key": {
+ "name": "settings_key",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "shared_host_auth_overrides": {
+ "name": "shared_host_auth_overrides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {
+ "shared_host_auth_overrides_host_user_protocol_unique": {
+ "name": "shared_host_auth_overrides_host_user_protocol_unique",
+ "columns": [
+ "host_id",
+ "user_id",
+ "protocol"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "shared_host_auth_overrides_host_id_ssh_data_id_fk": {
+ "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_user_id_users_id_fk": {
+ "name": "shared_host_auth_overrides_user_id_users_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_host_auth_overrides_id": {
+ "name": "shared_host_auth_overrides_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "shared_host_secrets": {
+ "name": "shared_host_secrets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_access_id": {
+ "name": "host_access_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('credential')"
+ },
+ "original_credential_id": {
+ "name": "original_credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_username": {
+ "name": "encrypted_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_auth_type": {
+ "name": "encrypted_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key": {
+ "name": "encrypted_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key_password": {
+ "name": "encrypted_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key_type": {
+ "name": "encrypted_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_domain": {
+ "name": "encrypted_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {
+ "idx_shared_host_secrets_scope": {
+ "name": "idx_shared_host_secrets_scope",
+ "columns": [
+ "host_access_id",
+ "target_user_id",
+ "protocol"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "shared_host_secrets_host_access_id_host_access_id_fk": {
+ "name": "shared_host_secrets_host_access_id_host_access_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "host_access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_target_user_id_users_id_fk": {
+ "name": "shared_host_secrets_target_user_id_users_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "original_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_host_secrets_id": {
+ "name": "shared_host_secrets_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "snippet_access": {
+ "name": "snippet_access",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "snippet_id": {
+ "name": "snippet_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('view')"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippet_access_snippet_id_snippets_id_fk": {
+ "name": "snippet_access_snippet_id_snippets_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "snippets",
+ "columnsFrom": [
+ "snippet_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_user_id_users_id_fk": {
+ "name": "snippet_access_user_id_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_role_id_roles_id_fk": {
+ "name": "snippet_access_role_id_roles_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_granted_by_users_id_fk": {
+ "name": "snippet_access_granted_by_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "snippet_access_id": {
+ "name": "snippet_access_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "snippet_folders": {
+ "name": "snippet_folders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippet_folders_user_id_users_id_fk": {
+ "name": "snippet_folders_user_id_users_id_fk",
+ "tableFrom": "snippet_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "snippet_folders_id": {
+ "name": "snippet_folders_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "snippet_folders_sync_id_unique": {
+ "name": "snippet_folders_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "snippets": {
+ "name": "snippets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "host_filter": {
+ "name": "host_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippets_user_id_users_id_fk": {
+ "name": "snippets_user_id_users_id_fk",
+ "tableFrom": "snippets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "snippets_id": {
+ "name": "snippets_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "snippets_sync_id_unique": {
+ "name": "snippets_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "ssh_credential_usage": {
+ "name": "ssh_credential_usage",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_credential_usage_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_host_id_ssh_data_id_fk": {
+ "name": "ssh_credential_usage_host_id_ssh_data_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_user_id_users_id_fk": {
+ "name": "ssh_credential_usage_user_id_users_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "ssh_credential_usage_id": {
+ "name": "ssh_credential_usage_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "ssh_credentials": {
+ "name": "ssh_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "detected_key_type": {
+ "name": "detected_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cert_public_key": {
+ "name": "cert_public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "usage_count": {
+ "name": "usage_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_credentials_user_id_users_id_fk": {
+ "name": "ssh_credentials_user_id_users_id_fk",
+ "tableFrom": "ssh_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "ssh_credentials_id": {
+ "name": "ssh_credentials_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "ssh_credentials_sync_id_unique": {
+ "name": "ssh_credentials_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "ssh_folders": {
+ "name": "ssh_folders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_folders_user_id_users_id_fk": {
+ "name": "ssh_folders_user_id_users_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_folders_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_folders_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "ssh_folders_id": {
+ "name": "ssh_folders_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "ssh_folders_sync_id_unique": {
+ "name": "ssh_folders_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "sso_providers": {
+ "name": "sso_providers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "sso_providers_id": {
+ "name": "sso_providers_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "sync_tombstones": {
+ "name": "sync_tombstones",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sync_tombstones_user_id_users_id_fk": {
+ "name": "sync_tombstones_user_id_users_id_fk",
+ "tableFrom": "sync_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sync_tombstones_id": {
+ "name": "sync_tombstones_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "termix_identities": {
+ "name": "termix_identities",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "handle": {
+ "name": "handle",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identities_user_id_users_id_fk": {
+ "name": "termix_identities_user_id_users_id_fk",
+ "tableFrom": "termix_identities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "termix_identities_id": {
+ "name": "termix_identities_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "termix_identities_user_id_unique": {
+ "name": "termix_identities_user_id_unique",
+ "columns": [
+ "user_id"
+ ]
+ },
+ "termix_identities_handle_unique": {
+ "name": "termix_identities_handle_unique",
+ "columns": [
+ "handle"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "termix_identity_ca": {
+ "name": "termix_identity_ca",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "validity_days": {
+ "name": "validity_days",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 90
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identity_ca_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_ca_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_ca_user_id_users_id_fk": {
+ "name": "termix_identity_ca_user_id_users_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "termix_identity_ca_id": {
+ "name": "termix_identity_ca_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "termix_identity_ca_identity_id_unique": {
+ "name": "termix_identity_ca_identity_id_unique",
+ "columns": [
+ "identity_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "termix_identity_keys": {
+ "name": "termix_identity_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "algorithm": {
+ "name": "algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('manual')"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identity_keys_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_keys_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_user_id_users_id_fk": {
+ "name": "termix_identity_keys_user_id_users_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_credential_id_ssh_credentials_id_fk": {
+ "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "termix_identity_keys_id": {
+ "name": "termix_identity_keys_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "tmux_session_tags": {
+ "name": "tmux_session_tags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "session_name": {
+ "name": "session_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tag": {
+ "name": "tag",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tmux_session_tags_user_id_users_id_fk": {
+ "name": "tmux_session_tags_user_id_users_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tmux_session_tags_host_id_ssh_data_id_fk": {
+ "name": "tmux_session_tags_host_id_ssh_data_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tmux_session_tags_id": {
+ "name": "tmux_session_tags_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "transfer_recent": {
+ "name": "transfer_recent",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_host_id": {
+ "name": "source_host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_host_id": {
+ "name": "dest_host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_path": {
+ "name": "dest_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_path_label": {
+ "name": "dest_path_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "transfer_recent_user_id_users_id_fk": {
+ "name": "transfer_recent_user_id_users_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_source_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_source_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "source_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_dest_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_dest_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "dest_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "transfer_recent_id": {
+ "name": "transfer_recent_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "trusted_devices": {
+ "name": "trusted_devices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_fingerprint": {
+ "name": "device_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "trusted_devices_user_id_users_id_fk": {
+ "name": "trusted_devices_user_id_users_id_fk",
+ "tableFrom": "trusted_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "trusted_devices_id": {
+ "name": "trusted_devices_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "user_open_tabs": {
+ "name": "user_open_tabs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_type": {
+ "name": "tab_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_order": {
+ "name": "tab_order",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "backend_session_id": {
+ "name": "backend_session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_open_tabs_user_id_users_id_fk": {
+ "name": "user_open_tabs_user_id_users_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_open_tabs_host_id_ssh_data_id_fk": {
+ "name": "user_open_tabs_host_id_ssh_data_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_open_tabs_id": {
+ "name": "user_open_tabs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reopen_tabs_on_login": {
+ "name": "reopen_tabs_on_login",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "font_size": {
+ "name": "font_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "accent_color": {
+ "name": "accent_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "language": {
+ "name": "language",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "storage_mode": {
+ "name": "storage_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "command_autocomplete": {
+ "name": "command_autocomplete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "command_palette_enabled": {
+ "name": "command_palette_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "show_host_tags": {
+ "name": "show_host_tags",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_tray_on_click": {
+ "name": "host_tray_on_click",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pin_app_rail": {
+ "name": "pin_app_rail",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expand_app_rail_on_hover": {
+ "name": "expand_app_rail_on_hover",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folders_collapsed": {
+ "name": "folders_collapsed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "confirm_snippet_execution": {
+ "name": "confirm_snippet_execution",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "disable_update_check": {
+ "name": "disable_update_check",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "confirm_tab_close": {
+ "name": "confirm_tab_close",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hidden_rail_tabs": {
+ "name": "hidden_rail_tabs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compact_host_view": {
+ "name": "compact_host_view",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status_color_scheme": {
+ "name": "status_color_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "custom_themes": {
+ "name": "custom_themes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "custom_keybindings": {
+ "name": "custom_keybindings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_preferences_user_id": {
+ "name": "user_preferences_user_id",
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_at": {
+ "name": "granted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {
+ "idx_user_roles_user_role": {
+ "name": "idx_user_roles_user_role",
+ "columns": [
+ "user_id",
+ "role_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_granted_by_users_id_fk": {
+ "name": "user_roles_granted_by_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_id": {
+ "name": "user_roles_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_admin": {
+ "name": "is_admin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_oidc": {
+ "name": "is_oidc",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "oidc_identifier": {
+ "name": "oidc_identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "authorization_url": {
+ "name": "authorization_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_url": {
+ "name": "token_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "identifier_path": {
+ "name": "identifier_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name_path": {
+ "name": "name_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "('openid email profile')"
+ },
+ "totp_secret": {
+ "name": "totp_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totp_enabled": {
+ "name": "totp_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "totp_backup_codes": {
+ "name": "totp_backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "registered_at": {
+ "name": "registered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "donation_modal_dismissed": {
+ "name": "donation_modal_dismissed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "vault_profiles": {
+ "name": "vault_profiles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vault_addr": {
+ "name": "vault_addr",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "vault_namespace": {
+ "name": "vault_namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_mount": {
+ "name": "oidc_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_role": {
+ "name": "oidc_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_mount": {
+ "name": "ssh_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_role": {
+ "name": "ssh_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "valid_principals": {
+ "name": "valid_principals",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "shared": {
+ "name": "shared",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "vault_profiles_user_id_users_id_fk": {
+ "name": "vault_profiles_user_id_users_id_fk",
+ "tableFrom": "vault_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "vault_profiles_id": {
+ "name": "vault_profiles_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "vault_profiles_sync_id_unique": {
+ "name": "vault_profiles_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "vault_tokens": {
+ "name": "vault_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "profile_id": {
+ "name": "profile_id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_vault_tokens_user_profile": {
+ "name": "idx_vault_tokens_user_profile",
+ "columns": [
+ "user_id",
+ "profile_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "vault_tokens_user_id_users_id_fk": {
+ "name": "vault_tokens_user_id_users_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "vault_tokens_profile_id_vault_profiles_id_fk": {
+ "name": "vault_tokens_profile_id_vault_profiles_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "vault_tokens_id": {
+ "name": "vault_tokens_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "webauthn_credentials": {
+ "name": "webauthn_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "counter": {
+ "name": "counter",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_verification": {
+ "name": "user_verification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('preferred')"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(CURRENT_TIMESTAMP)"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "webauthn_credentials_user_id_users_id_fk": {
+ "name": "webauthn_credentials_user_id_users_id_fk",
+ "tableFrom": "webauthn_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "webauthn_credentials_id": {
+ "name": "webauthn_credentials_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json
new file mode 100644
index 00000000..adef7524
--- /dev/null
+++ b/drizzle/mysql/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "mysql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "5",
+ "when": 1785738871436,
+ "tag": "0000_clean_pretty_boy",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/drizzle/postgres/0000_jazzy_infant_terrible.sql b/drizzle/postgres/0000_jazzy_infant_terrible.sql
new file mode 100644
index 00000000..dd4c9317
--- /dev/null
+++ b/drizzle/postgres/0000_jazzy_infant_terrible.sql
@@ -0,0 +1,837 @@
+CREATE TABLE "alert_firings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "rule_id" integer NOT NULL,
+ "host_id" integer NOT NULL,
+ "host_name" text NOT NULL,
+ "fired_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "resolved_at" text,
+ "value" double precision,
+ "message" text NOT NULL,
+ "severity" text DEFAULT 'warning' NOT NULL,
+ "acknowledged" boolean DEFAULT false NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "alert_rule_channels" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "rule_id" integer NOT NULL,
+ "channel_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "alert_rules" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer,
+ "name" varchar(255) NOT NULL,
+ "enabled" boolean DEFAULT true NOT NULL,
+ "trigger_type" text NOT NULL,
+ "threshold_value" double precision,
+ "threshold_duration_seconds" integer,
+ "cooldown_minutes" integer DEFAULT 15 NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "api_keys" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "token_hash" text NOT NULL,
+ "token_prefix" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text,
+ "last_used_at" text,
+ "is_active" boolean DEFAULT true NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "audit_logs" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255),
+ "username" text NOT NULL,
+ "action" text NOT NULL,
+ "resource_type" text NOT NULL,
+ "resource_id" text,
+ "resource_name" text,
+ "details" text,
+ "ip_address" text,
+ "user_agent" text,
+ "success" boolean NOT NULL,
+ "error_message" text,
+ "timestamp" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "c2s_tunnel_presets" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "config" text NOT NULL,
+ "platform" text,
+ "computer_name" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "command_history" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "command" text NOT NULL,
+ "executed_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "dashboard_service_links" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "label" text NOT NULL,
+ "url" text NOT NULL,
+ "order" integer DEFAULT 0 NOT NULL,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "dashboard_service_links_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "dismissed_alerts" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "alert_id" text NOT NULL,
+ "dismissed_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "file_manager_pinned" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "path" text NOT NULL,
+ "pinned_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "file_manager_recent" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "path" text NOT NULL,
+ "last_opened" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "file_manager_shortcuts" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "path" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "homepage_items" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "type_id" text NOT NULL,
+ "title" text,
+ "config" text DEFAULT '{}' NOT NULL,
+ "folder_id" integer,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "homepage_items_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "homepage_layouts" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "layout" text DEFAULT '{}' NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "homepage_layouts_user_id_unique" UNIQUE("user_id")
+);
+--> statement-breakpoint
+CREATE TABLE "host_access" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "host_id" integer NOT NULL,
+ "user_id" varchar(255),
+ "role_id" integer,
+ "granted_by" varchar(255) NOT NULL,
+ "permission_level" text DEFAULT 'connect' NOT NULL,
+ "expires_at" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "last_accessed_at" text,
+ "access_count" integer DEFAULT 0 NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "host_health_checks" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "checks" text NOT NULL,
+ "interval_seconds" integer DEFAULT 300 NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "host_health_history" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "check_id" text NOT NULL,
+ "ts" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "ok" boolean NOT NULL,
+ "latency_ms" integer,
+ "detail" text
+);
+--> statement-breakpoint
+CREATE TABLE "host_metrics_history" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "host_id" integer NOT NULL,
+ "ts" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "cpu_percent" double precision,
+ "mem_percent" double precision,
+ "disk_percent" double precision,
+ "net_rx_bytes" integer,
+ "net_tx_bytes" integer
+);
+--> statement-breakpoint
+CREATE TABLE "host_metrics_preferences" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "layout" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "ssh_data" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "connection_type" text DEFAULT 'ssh' NOT NULL,
+ "name" varchar(255),
+ "ip" text NOT NULL,
+ "port" integer NOT NULL,
+ "username" text NOT NULL,
+ "folder" text,
+ "tags" text,
+ "pin" boolean DEFAULT false NOT NULL,
+ "auth_type" text NOT NULL,
+ "use_warpgate" boolean DEFAULT false NOT NULL,
+ "share_ssh_auth" boolean DEFAULT false NOT NULL,
+ "force_keyboard_interactive" text,
+ "password" text,
+ "key" text,
+ "key_password" text,
+ "key_type" text,
+ "sudo_password" text,
+ "autostart_password" text,
+ "autostart_key" text,
+ "autostart_key_password" text,
+ "credential_id" integer,
+ "override_credential_username" boolean,
+ "vault_profile_id" integer,
+ "enable_terminal" boolean DEFAULT true NOT NULL,
+ "enable_session_logging" boolean DEFAULT true NOT NULL,
+ "allow_session_sharing" boolean DEFAULT true NOT NULL,
+ "enable_command_history" boolean DEFAULT true NOT NULL,
+ "enable_tunnel" boolean DEFAULT true NOT NULL,
+ "tunnel_connections" text,
+ "jump_hosts" text,
+ "enable_file_manager" boolean DEFAULT true NOT NULL,
+ "scp_legacy" boolean DEFAULT false NOT NULL,
+ "enable_docker" boolean DEFAULT false NOT NULL,
+ "enable_tmux_monitor" boolean DEFAULT false NOT NULL,
+ "show_terminal_in_sidebar" boolean DEFAULT true NOT NULL,
+ "show_file_manager_in_sidebar" boolean DEFAULT false NOT NULL,
+ "show_tunnel_in_sidebar" boolean DEFAULT false NOT NULL,
+ "show_docker_in_sidebar" boolean DEFAULT false NOT NULL,
+ "show_server_stats_in_sidebar" boolean DEFAULT false NOT NULL,
+ "default_path" text,
+ "stats_config" text,
+ "docker_config" text,
+ "enable_proxmox" boolean DEFAULT false NOT NULL,
+ "proxmox_config" text,
+ "terminal_config" text,
+ "quick_actions" text,
+ "notes" text,
+ "enable_ssh" boolean DEFAULT true NOT NULL,
+ "enable_rdp" boolean DEFAULT false NOT NULL,
+ "enable_vnc" boolean DEFAULT false NOT NULL,
+ "enable_telnet" boolean DEFAULT false NOT NULL,
+ "ssh_port" integer DEFAULT 22,
+ "rdp_port" integer DEFAULT 3389,
+ "vnc_port" integer DEFAULT 5900,
+ "telnet_port" integer DEFAULT 23,
+ "rdp_credential_id" integer,
+ "rdp_user" text,
+ "rdp_password" text,
+ "rdp_domain" text,
+ "rdp_security" text,
+ "rdp_ignore_cert" boolean DEFAULT false,
+ "vnc_credential_id" integer,
+ "vnc_password" text,
+ "vnc_user" text,
+ "telnet_user" text,
+ "telnet_password" text,
+ "telnet_credential_id" integer,
+ "rdp_auth_type" text,
+ "vnc_auth_type" text,
+ "telnet_auth_type" text,
+ "domain" text,
+ "security" text,
+ "ignore_cert" boolean DEFAULT false,
+ "guacamole_config" text,
+ "use_socks5" boolean,
+ "socks5_host" text,
+ "socks5_port" integer,
+ "socks5_username" text,
+ "socks5_password" text,
+ "socks5_proxy_chain" text,
+ "connection_origin" text,
+ "mac_address" text,
+ "wol_broadcast_address" text,
+ "port_knock_sequence" text,
+ "host_key_fingerprint" text,
+ "host_key_type" text,
+ "host_key_algorithm" text DEFAULT 'sha256',
+ "host_key_first_seen" text,
+ "host_key_last_verified" text,
+ "host_key_changed_count" integer DEFAULT 0,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "ssh_data_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "network_topology" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "topology" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "notification_channels" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "type" text NOT NULL,
+ "config" text NOT NULL,
+ "enabled" boolean DEFAULT true NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "opkssh_tokens" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "ssh_cert" text NOT NULL,
+ "private_key" text NOT NULL,
+ "email" text,
+ "sub" text,
+ "issuer" text,
+ "audience" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text NOT NULL,
+ "last_used" text
+);
+--> statement-breakpoint
+CREATE TABLE "recent_activity" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "type" text NOT NULL,
+ "host_id" integer NOT NULL,
+ "host_name" text,
+ "timestamp" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "roles" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "display_name" text NOT NULL,
+ "description" text,
+ "is_system" boolean DEFAULT false NOT NULL,
+ "permissions" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "roles_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "session_recordings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "host_id" integer NOT NULL,
+ "user_id" varchar(255),
+ "username" text,
+ "access_id" integer,
+ "started_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "ended_at" text,
+ "duration" integer,
+ "commands" text,
+ "dangerous_actions" text,
+ "recording_path" text,
+ "protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
+ "format" text DEFAULT 'text' NOT NULL,
+ "terminated_by_owner" boolean DEFAULT false,
+ "termination_reason" text
+);
+--> statement-breakpoint
+CREATE TABLE "session_share_participants" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "share_id" varchar(255) NOT NULL,
+ "user_id" varchar(255),
+ "guest_label" text,
+ "joined_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "left_at" text
+);
+--> statement-breakpoint
+CREATE TABLE "session_shares" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "host_id" integer NOT NULL,
+ "owner_user_id" varchar(255) NOT NULL,
+ "protocol" varchar(255) NOT NULL,
+ "session_id" text NOT NULL,
+ "tab_instance_id" text,
+ "share_type" text NOT NULL,
+ "target_user_id" varchar(255),
+ "link_token" varchar(255),
+ "permission_level" text DEFAULT 'read-only' NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text NOT NULL,
+ "revoked_at" text,
+ "last_joined_at" text,
+ "join_count" integer DEFAULT 0 NOT NULL,
+ CONSTRAINT "session_shares_link_token_unique" UNIQUE("link_token")
+);
+--> statement-breakpoint
+CREATE TABLE "sessions" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "jwt_token" text NOT NULL,
+ "device_type" text NOT NULL,
+ "device_info" text NOT NULL,
+ "oidc_sub" text,
+ "oidc_sid" text,
+ "sso_provider_id" integer,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text NOT NULL,
+ "last_active_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "settings" (
+ "key" varchar(255) PRIMARY KEY NOT NULL,
+ "value" text NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "shared_host_auth_overrides" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "host_id" integer NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
+ "credential_id" integer NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "shared_host_secrets" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "host_access_id" integer NOT NULL,
+ "target_user_id" varchar(255) NOT NULL,
+ "protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
+ "source_type" text DEFAULT 'credential' NOT NULL,
+ "original_credential_id" integer,
+ "encrypted_username" text,
+ "encrypted_auth_type" text,
+ "encrypted_password" text,
+ "encrypted_key" text,
+ "encrypted_key_password" text,
+ "encrypted_key_type" text,
+ "encrypted_domain" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "snippet_access" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "snippet_id" integer NOT NULL,
+ "user_id" varchar(255),
+ "role_id" integer,
+ "granted_by" varchar(255) NOT NULL,
+ "permission_level" text DEFAULT 'view' NOT NULL,
+ "expires_at" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "snippet_folders" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "color" text,
+ "icon" text,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "snippet_folders_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "snippets" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "content" text NOT NULL,
+ "description" text,
+ "folder" text,
+ "order" integer DEFAULT 0 NOT NULL,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "host_filter" text,
+ CONSTRAINT "snippets_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "ssh_credential_usage" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "credential_id" integer NOT NULL,
+ "host_id" integer NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "used_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "ssh_credentials" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "description" text,
+ "folder" text,
+ "tags" text,
+ "auth_type" text NOT NULL,
+ "username" text,
+ "password" text,
+ "key" text,
+ "private_key" text,
+ "public_key" text,
+ "key_password" text,
+ "key_type" text,
+ "detected_key_type" text,
+ "cert_public_key" text,
+ "usage_count" integer DEFAULT 0 NOT NULL,
+ "last_used" text,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "ssh_credentials_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "ssh_folders" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "color" text,
+ "icon" text,
+ "credential_id" integer,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "ssh_folders_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "sso_providers" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "type" text NOT NULL,
+ "enabled" boolean DEFAULT true NOT NULL,
+ "display_order" integer DEFAULT 0 NOT NULL,
+ "config" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "sync_tombstones" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "entity_type" text NOT NULL,
+ "sync_id" varchar(255) NOT NULL,
+ "deleted_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "termix_identities" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "handle" varchar(255) NOT NULL,
+ "description" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "termix_identities_user_id_unique" UNIQUE("user_id"),
+ CONSTRAINT "termix_identities_handle_unique" UNIQUE("handle")
+);
+--> statement-breakpoint
+CREATE TABLE "termix_identity_ca" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "identity_id" integer NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "public_key" text NOT NULL,
+ "private_key" text NOT NULL,
+ "validity_days" integer DEFAULT 90 NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "termix_identity_ca_identity_id_unique" UNIQUE("identity_id")
+);
+--> statement-breakpoint
+CREATE TABLE "termix_identity_keys" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "identity_id" integer NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "public_key" text NOT NULL,
+ "key_type" text NOT NULL,
+ "algorithm" text NOT NULL,
+ "label" text,
+ "comment" text,
+ "source" text DEFAULT 'manual' NOT NULL,
+ "credential_id" integer,
+ "enabled" boolean DEFAULT true NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "tmux_session_tags" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "host_id" integer NOT NULL,
+ "session_name" text NOT NULL,
+ "tag" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "transfer_recent" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "source_host_id" integer NOT NULL,
+ "dest_host_id" integer NOT NULL,
+ "dest_path" text NOT NULL,
+ "dest_path_label" text NOT NULL,
+ "last_used" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "trusted_devices" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "device_fingerprint" text NOT NULL,
+ "device_type" text NOT NULL,
+ "device_info" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text NOT NULL,
+ "last_used_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "user_open_tabs" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "tab_type" text NOT NULL,
+ "host_id" integer,
+ "label" text NOT NULL,
+ "tab_order" integer DEFAULT 0 NOT NULL,
+ "backend_session_id" text,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "user_preferences" (
+ "user_id" varchar(255) PRIMARY KEY NOT NULL,
+ "reopen_tabs_on_login" boolean DEFAULT false NOT NULL,
+ "theme" text,
+ "font_size" text,
+ "accent_color" text,
+ "language" text,
+ "storage_mode" text,
+ "command_autocomplete" boolean,
+ "command_palette_enabled" boolean,
+ "show_host_tags" boolean,
+ "host_tray_on_click" boolean,
+ "pin_app_rail" boolean,
+ "expand_app_rail_on_hover" boolean,
+ "folders_collapsed" boolean,
+ "confirm_snippet_execution" boolean,
+ "disable_update_check" boolean,
+ "confirm_tab_close" boolean,
+ "hidden_rail_tabs" text,
+ "compact_host_view" boolean,
+ "status_color_scheme" text,
+ "custom_themes" text,
+ "custom_keybindings" text,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "user_roles" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "role_id" integer NOT NULL,
+ "granted_by" varchar(255),
+ "granted_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "users" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "username" text NOT NULL,
+ "password_hash" text NOT NULL,
+ "is_admin" boolean DEFAULT false NOT NULL,
+ "is_oidc" boolean DEFAULT false NOT NULL,
+ "oidc_identifier" text,
+ "sso_provider_id" integer,
+ "client_id" text,
+ "client_secret" text,
+ "issuer_url" text,
+ "authorization_url" text,
+ "token_url" text,
+ "identifier_path" text,
+ "name_path" text,
+ "scopes" text DEFAULT 'openid email profile',
+ "totp_secret" text,
+ "totp_enabled" boolean DEFAULT false NOT NULL,
+ "totp_backup_codes" text,
+ "registered_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "donation_modal_dismissed" boolean DEFAULT false NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "vault_profiles" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "description" text,
+ "folder" text,
+ "tags" text,
+ "vault_addr" text NOT NULL,
+ "vault_namespace" text,
+ "oidc_mount" text,
+ "oidc_role" text,
+ "ssh_mount" text,
+ "ssh_role" text NOT NULL,
+ "valid_principals" text,
+ "key_type" text,
+ "shared" boolean DEFAULT false NOT NULL,
+ "sync_id" varchar(255),
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ CONSTRAINT "vault_profiles_sync_id_unique" UNIQUE("sync_id")
+);
+--> statement-breakpoint
+CREATE TABLE "vault_tokens" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "profile_id" integer NOT NULL,
+ "ssh_cert" text NOT NULL,
+ "private_key" text NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "expires_at" text NOT NULL,
+ "last_used" text
+);
+--> statement-breakpoint
+CREATE TABLE "webauthn_credentials" (
+ "id" varchar(255) PRIMARY KEY NOT NULL,
+ "user_id" varchar(255) NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "credential_id" text NOT NULL,
+ "public_key" text NOT NULL,
+ "counter" integer DEFAULT 0 NOT NULL,
+ "device_type" text,
+ "backed_up" boolean DEFAULT false NOT NULL,
+ "transports" text,
+ "user_verification" text DEFAULT 'preferred' NOT NULL,
+ "created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ "last_used_at" text
+);
+--> statement-breakpoint
+ALTER TABLE "alert_firings" ADD CONSTRAINT "alert_firings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "alert_firings" ADD CONSTRAINT "alert_firings_rule_id_alert_rules_id_fk" FOREIGN KEY ("rule_id") REFERENCES "public"."alert_rules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "alert_rule_channels" ADD CONSTRAINT "alert_rule_channels_rule_id_alert_rules_id_fk" FOREIGN KEY ("rule_id") REFERENCES "public"."alert_rules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "alert_rule_channels" ADD CONSTRAINT "alert_rule_channels_channel_id_notification_channels_id_fk" FOREIGN KEY ("channel_id") REFERENCES "public"."notification_channels"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "alert_rules" ADD CONSTRAINT "alert_rules_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "alert_rules" ADD CONSTRAINT "alert_rules_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "c2s_tunnel_presets" ADD CONSTRAINT "c2s_tunnel_presets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "command_history" ADD CONSTRAINT "command_history_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "command_history" ADD CONSTRAINT "command_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "dashboard_service_links" ADD CONSTRAINT "dashboard_service_links_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "dismissed_alerts" ADD CONSTRAINT "dismissed_alerts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_pinned" ADD CONSTRAINT "file_manager_pinned_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_pinned" ADD CONSTRAINT "file_manager_pinned_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_recent" ADD CONSTRAINT "file_manager_recent_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_recent" ADD CONSTRAINT "file_manager_recent_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_shortcuts" ADD CONSTRAINT "file_manager_shortcuts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "file_manager_shortcuts" ADD CONSTRAINT "file_manager_shortcuts_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "homepage_items" ADD CONSTRAINT "homepage_items_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "homepage_layouts" ADD CONSTRAINT "homepage_layouts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_access" ADD CONSTRAINT "host_access_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_access" ADD CONSTRAINT "host_access_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_access" ADD CONSTRAINT "host_access_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_access" ADD CONSTRAINT "host_access_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_health_checks" ADD CONSTRAINT "host_health_checks_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_health_checks" ADD CONSTRAINT "host_health_checks_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_health_history" ADD CONSTRAINT "host_health_history_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_health_history" ADD CONSTRAINT "host_health_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_metrics_history" ADD CONSTRAINT "host_metrics_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_metrics_preferences" ADD CONSTRAINT "host_metrics_preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_metrics_preferences" ADD CONSTRAINT "host_metrics_preferences_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_vault_profile_id_vault_profiles_id_fk" FOREIGN KEY ("vault_profile_id") REFERENCES "public"."vault_profiles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_rdp_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("rdp_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_vnc_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("vnc_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_telnet_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("telnet_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "network_topology" ADD CONSTRAINT "network_topology_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "notification_channels" ADD CONSTRAINT "notification_channels_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "opkssh_tokens" ADD CONSTRAINT "opkssh_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "opkssh_tokens" ADD CONSTRAINT "opkssh_tokens_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "recent_activity" ADD CONSTRAINT "recent_activity_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "recent_activity" ADD CONSTRAINT "recent_activity_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_access_id_host_access_id_fk" FOREIGN KEY ("access_id") REFERENCES "public"."host_access"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_share_participants" ADD CONSTRAINT "session_share_participants_share_id_session_shares_id_fk" FOREIGN KEY ("share_id") REFERENCES "public"."session_shares"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_share_participants" ADD CONSTRAINT "session_share_participants_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_target_user_id_users_id_fk" FOREIGN KEY ("target_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_host_access_id_host_access_id_fk" FOREIGN KEY ("host_access_id") REFERENCES "public"."host_access"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_target_user_id_users_id_fk" FOREIGN KEY ("target_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_original_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("original_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_snippet_id_snippets_id_fk" FOREIGN KEY ("snippet_id") REFERENCES "public"."snippets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippet_folders" ADD CONSTRAINT "snippet_folders_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "snippets" ADD CONSTRAINT "snippets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_credentials" ADD CONSTRAINT "ssh_credentials_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_folders" ADD CONSTRAINT "ssh_folders_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ssh_folders" ADD CONSTRAINT "ssh_folders_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "sync_tombstones" ADD CONSTRAINT "sync_tombstones_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identities" ADD CONSTRAINT "termix_identities_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identity_ca" ADD CONSTRAINT "termix_identity_ca_identity_id_termix_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "public"."termix_identities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identity_ca" ADD CONSTRAINT "termix_identity_ca_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_identity_id_termix_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "public"."termix_identities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "tmux_session_tags" ADD CONSTRAINT "tmux_session_tags_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "tmux_session_tags" ADD CONSTRAINT "tmux_session_tags_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_source_host_id_ssh_data_id_fk" FOREIGN KEY ("source_host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_dest_host_id_ssh_data_id_fk" FOREIGN KEY ("dest_host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "trusted_devices" ADD CONSTRAINT "trusted_devices_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_open_tabs" ADD CONSTRAINT "user_open_tabs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_open_tabs" ADD CONSTRAINT "user_open_tabs_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "vault_profiles" ADD CONSTRAINT "vault_profiles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "vault_tokens" ADD CONSTRAINT "vault_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "vault_tokens" ADD CONSTRAINT "vault_tokens_profile_id_vault_profiles_id_fk" FOREIGN KEY ("profile_id") REFERENCES "public"."vault_profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "webauthn_credentials" ADD CONSTRAINT "webauthn_credentials_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_host_health_checks_user_host" ON "host_health_checks" USING btree ("user_id","host_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_host_metrics_prefs_user_host" ON "host_metrics_preferences" USING btree ("user_id","host_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_opkssh_tokens_user_host" ON "opkssh_tokens" USING btree ("user_id","host_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "shared_host_auth_overrides_host_user_protocol_unique" ON "shared_host_auth_overrides" USING btree ("host_id","user_id","protocol");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_shared_host_secrets_scope" ON "shared_host_secrets" USING btree ("host_access_id","target_user_id","protocol");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_user_roles_user_role" ON "user_roles" USING btree ("user_id","role_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_vault_tokens_user_profile" ON "vault_tokens" USING btree ("user_id","profile_id");
\ No newline at end of file
diff --git a/drizzle/postgres/meta/0000_snapshot.json b/drizzle/postgres/meta/0000_snapshot.json
new file mode 100644
index 00000000..3ace5944
--- /dev/null
+++ b/drizzle/postgres/meta/0000_snapshot.json
@@ -0,0 +1,5778 @@
+{
+ "id": "3d15ec30-87a9-4af4-855a-75e325a74c5d",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.alert_firings": {
+ "name": "alert_firings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fired_at": {
+ "name": "fired_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "value": {
+ "name": "value",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "severity": {
+ "name": "severity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'warning'"
+ },
+ "acknowledged": {
+ "name": "acknowledged",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_firings_user_id_users_id_fk": {
+ "name": "alert_firings_user_id_users_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_firings_rule_id_alert_rules_id_fk": {
+ "name": "alert_firings_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.alert_rule_channels": {
+ "name": "alert_rule_channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rule_channels_rule_id_alert_rules_id_fk": {
+ "name": "alert_rule_channels_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rule_channels_channel_id_notification_channels_id_fk": {
+ "name": "alert_rule_channels_channel_id_notification_channels_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.alert_rules": {
+ "name": "alert_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "threshold_value": {
+ "name": "threshold_value",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "threshold_duration_seconds": {
+ "name": "threshold_duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cooldown_minutes": {
+ "name": "cooldown_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 15
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rules_user_id_users_id_fk": {
+ "name": "alert_rules_user_id_users_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rules_host_id_ssh_data_id_fk": {
+ "name": "alert_rules_host_id_ssh_data_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.api_keys": {
+ "name": "api_keys",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_keys_user_id_users_id_fk": {
+ "name": "api_keys_user_id_users_id_fk",
+ "tableFrom": "api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "success": {
+ "name": "success",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.c2s_tunnel_presets": {
+ "name": "c2s_tunnel_presets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "computer_name": {
+ "name": "computer_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "c2s_tunnel_presets_user_id_users_id_fk": {
+ "name": "c2s_tunnel_presets_user_id_users_id_fk",
+ "tableFrom": "c2s_tunnel_presets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.command_history": {
+ "name": "command_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "executed_at": {
+ "name": "executed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "command_history_user_id_users_id_fk": {
+ "name": "command_history_user_id_users_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "command_history_host_id_ssh_data_id_fk": {
+ "name": "command_history_host_id_ssh_data_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.dashboard_service_links": {
+ "name": "dashboard_service_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dashboard_service_links_user_id_users_id_fk": {
+ "name": "dashboard_service_links_user_id_users_id_fk",
+ "tableFrom": "dashboard_service_links",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "dashboard_service_links_sync_id_unique": {
+ "name": "dashboard_service_links_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.dismissed_alerts": {
+ "name": "dismissed_alerts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "alert_id": {
+ "name": "alert_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dismissed_at": {
+ "name": "dismissed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dismissed_alerts_user_id_users_id_fk": {
+ "name": "dismissed_alerts_user_id_users_id_fk",
+ "tableFrom": "dismissed_alerts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.file_manager_pinned": {
+ "name": "file_manager_pinned",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_pinned_user_id_users_id_fk": {
+ "name": "file_manager_pinned_user_id_users_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_pinned_host_id_ssh_data_id_fk": {
+ "name": "file_manager_pinned_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.file_manager_recent": {
+ "name": "file_manager_recent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_opened": {
+ "name": "last_opened",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_recent_user_id_users_id_fk": {
+ "name": "file_manager_recent_user_id_users_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_recent_host_id_ssh_data_id_fk": {
+ "name": "file_manager_recent_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.file_manager_shortcuts": {
+ "name": "file_manager_shortcuts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_shortcuts_user_id_users_id_fk": {
+ "name": "file_manager_shortcuts_user_id_users_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_shortcuts_host_id_ssh_data_id_fk": {
+ "name": "file_manager_shortcuts_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.homepage_items": {
+ "name": "homepage_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type_id": {
+ "name": "type_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "homepage_items_user_id_users_id_fk": {
+ "name": "homepage_items_user_id_users_id_fk",
+ "tableFrom": "homepage_items",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "homepage_items_sync_id_unique": {
+ "name": "homepage_items_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.homepage_layouts": {
+ "name": "homepage_layouts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "homepage_layouts_user_id_users_id_fk": {
+ "name": "homepage_layouts_user_id_users_id_fk",
+ "tableFrom": "homepage_layouts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "homepage_layouts_user_id_unique": {
+ "name": "homepage_layouts_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_access": {
+ "name": "host_access",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'connect'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "last_accessed_at": {
+ "name": "last_accessed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_count": {
+ "name": "access_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_access_host_id_ssh_data_id_fk": {
+ "name": "host_access_host_id_ssh_data_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_user_id_users_id_fk": {
+ "name": "host_access_user_id_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_role_id_roles_id_fk": {
+ "name": "host_access_role_id_roles_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_granted_by_users_id_fk": {
+ "name": "host_access_granted_by_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_health_checks": {
+ "name": "host_health_checks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checks": {
+ "name": "checks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 300
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_host_health_checks_user_host": {
+ "name": "idx_host_health_checks_user_host",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "host_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_health_checks_user_id_users_id_fk": {
+ "name": "host_health_checks_user_id_users_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_checks_host_id_ssh_data_id_fk": {
+ "name": "host_health_checks_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_health_history": {
+ "name": "host_health_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "check_id": {
+ "name": "check_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "ok": {
+ "name": "ok",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_health_history_user_id_users_id_fk": {
+ "name": "host_health_history_user_id_users_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_history_host_id_ssh_data_id_fk": {
+ "name": "host_health_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics_history": {
+ "name": "host_metrics_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mem_percent": {
+ "name": "mem_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disk_percent": {
+ "name": "disk_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "net_rx_bytes": {
+ "name": "net_rx_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "net_tx_bytes": {
+ "name": "net_tx_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_metrics_history_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics_preferences": {
+ "name": "host_metrics_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_host_metrics_prefs_user_host": {
+ "name": "idx_host_metrics_prefs_user_host",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "host_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_preferences_user_id_users_id_fk": {
+ "name": "host_metrics_preferences_user_id_users_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_metrics_preferences_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_preferences_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh_data": {
+ "name": "ssh_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ssh'"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip": {
+ "name": "ip",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pin": {
+ "name": "pin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "use_warpgate": {
+ "name": "use_warpgate",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "share_ssh_auth": {
+ "name": "share_ssh_auth",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "force_keyboard_interactive": {
+ "name": "force_keyboard_interactive",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sudo_password": {
+ "name": "sudo_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autostart_password": {
+ "name": "autostart_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autostart_key": {
+ "name": "autostart_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autostart_key_password": {
+ "name": "autostart_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override_credential_username": {
+ "name": "override_credential_username",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vault_profile_id": {
+ "name": "vault_profile_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_terminal": {
+ "name": "enable_terminal",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "enable_session_logging": {
+ "name": "enable_session_logging",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "allow_session_sharing": {
+ "name": "allow_session_sharing",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "enable_command_history": {
+ "name": "enable_command_history",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "enable_tunnel": {
+ "name": "enable_tunnel",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "tunnel_connections": {
+ "name": "tunnel_connections",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jump_hosts": {
+ "name": "jump_hosts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_file_manager": {
+ "name": "enable_file_manager",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "scp_legacy": {
+ "name": "scp_legacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enable_docker": {
+ "name": "enable_docker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enable_tmux_monitor": {
+ "name": "enable_tmux_monitor",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "show_terminal_in_sidebar": {
+ "name": "show_terminal_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "show_file_manager_in_sidebar": {
+ "name": "show_file_manager_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "show_tunnel_in_sidebar": {
+ "name": "show_tunnel_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "show_docker_in_sidebar": {
+ "name": "show_docker_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "show_server_stats_in_sidebar": {
+ "name": "show_server_stats_in_sidebar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "default_path": {
+ "name": "default_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stats_config": {
+ "name": "stats_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "docker_config": {
+ "name": "docker_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_proxmox": {
+ "name": "enable_proxmox",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "proxmox_config": {
+ "name": "proxmox_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "terminal_config": {
+ "name": "terminal_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quick_actions": {
+ "name": "quick_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_ssh": {
+ "name": "enable_ssh",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "enable_rdp": {
+ "name": "enable_rdp",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enable_vnc": {
+ "name": "enable_vnc",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enable_telnet": {
+ "name": "enable_telnet",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "ssh_port": {
+ "name": "ssh_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 22
+ },
+ "rdp_port": {
+ "name": "rdp_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3389
+ },
+ "vnc_port": {
+ "name": "vnc_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 5900
+ },
+ "telnet_port": {
+ "name": "telnet_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 23
+ },
+ "rdp_credential_id": {
+ "name": "rdp_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_user": {
+ "name": "rdp_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_password": {
+ "name": "rdp_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_domain": {
+ "name": "rdp_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_security": {
+ "name": "rdp_security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_ignore_cert": {
+ "name": "rdp_ignore_cert",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "vnc_credential_id": {
+ "name": "vnc_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vnc_password": {
+ "name": "vnc_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vnc_user": {
+ "name": "vnc_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telnet_user": {
+ "name": "telnet_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telnet_password": {
+ "name": "telnet_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telnet_credential_id": {
+ "name": "telnet_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rdp_auth_type": {
+ "name": "rdp_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vnc_auth_type": {
+ "name": "vnc_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telnet_auth_type": {
+ "name": "telnet_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "security": {
+ "name": "security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ignore_cert": {
+ "name": "ignore_cert",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "guacamole_config": {
+ "name": "guacamole_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "use_socks5": {
+ "name": "use_socks5",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "socks5_host": {
+ "name": "socks5_host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "socks5_port": {
+ "name": "socks5_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "socks5_username": {
+ "name": "socks5_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "socks5_password": {
+ "name": "socks5_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "socks5_proxy_chain": {
+ "name": "socks5_proxy_chain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_origin": {
+ "name": "connection_origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mac_address": {
+ "name": "mac_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wol_broadcast_address": {
+ "name": "wol_broadcast_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "port_knock_sequence": {
+ "name": "port_knock_sequence",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_key_fingerprint": {
+ "name": "host_key_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_key_type": {
+ "name": "host_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_key_algorithm": {
+ "name": "host_key_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'sha256'"
+ },
+ "host_key_first_seen": {
+ "name": "host_key_first_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_key_last_verified": {
+ "name": "host_key_last_verified",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_key_changed_count": {
+ "name": "host_key_changed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_data_user_id_users_id_fk": {
+ "name": "ssh_data_user_id_users_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_data_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vault_profile_id_vault_profiles_id_fk": {
+ "name": "ssh_data_vault_profile_id_vault_profiles_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "vault_profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_rdp_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "rdp_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vnc_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "vnc_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_telnet_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "telnet_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "ssh_data_sync_id_unique": {
+ "name": "ssh_data_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.network_topology": {
+ "name": "network_topology",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topology": {
+ "name": "topology",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "network_topology_user_id_users_id_fk": {
+ "name": "network_topology_user_id_users_id_fk",
+ "tableFrom": "network_topology",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification_channels": {
+ "name": "notification_channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_channels_user_id_users_id_fk": {
+ "name": "notification_channels_user_id_users_id_fk",
+ "tableFrom": "notification_channels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.opkssh_tokens": {
+ "name": "opkssh_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sub": {
+ "name": "sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_opkssh_tokens_user_host": {
+ "name": "idx_opkssh_tokens_user_host",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "host_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "opkssh_tokens_user_id_users_id_fk": {
+ "name": "opkssh_tokens_user_id_users_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "opkssh_tokens_host_id_ssh_data_id_fk": {
+ "name": "opkssh_tokens_host_id_ssh_data_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recent_activity": {
+ "name": "recent_activity",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recent_activity_user_id_users_id_fk": {
+ "name": "recent_activity_user_id_users_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "recent_activity_host_id_ssh_data_id_fk": {
+ "name": "recent_activity_host_id_ssh_data_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.roles": {
+ "name": "roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session_recordings": {
+ "name": "session_recordings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_id": {
+ "name": "access_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "commands": {
+ "name": "commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dangerous_actions": {
+ "name": "dangerous_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recording_path": {
+ "name": "recording_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ssh'"
+ },
+ "format": {
+ "name": "format",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text'"
+ },
+ "terminated_by_owner": {
+ "name": "terminated_by_owner",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "termination_reason": {
+ "name": "termination_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_recordings_host_id_ssh_data_id_fk": {
+ "name": "session_recordings_host_id_ssh_data_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_recordings_user_id_users_id_fk": {
+ "name": "session_recordings_user_id_users_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "session_recordings_access_id_host_access_id_fk": {
+ "name": "session_recordings_access_id_host_access_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session_share_participants": {
+ "name": "session_share_participants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "share_id": {
+ "name": "share_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "guest_label": {
+ "name": "guest_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "left_at": {
+ "name": "left_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_share_participants_share_id_session_shares_id_fk": {
+ "name": "session_share_participants_share_id_session_shares_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "session_shares",
+ "columnsFrom": [
+ "share_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_share_participants_user_id_users_id_fk": {
+ "name": "session_share_participants_user_id_users_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session_shares": {
+ "name": "session_shares",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tab_instance_id": {
+ "name": "tab_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "share_type": {
+ "name": "share_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "link_token": {
+ "name": "link_token",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'read-only'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_joined_at": {
+ "name": "last_joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "join_count": {
+ "name": "join_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_shares_host_id_ssh_data_id_fk": {
+ "name": "session_shares_host_id_ssh_data_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_owner_user_id_users_id_fk": {
+ "name": "session_shares_owner_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_target_user_id_users_id_fk": {
+ "name": "session_shares_target_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_shares_link_token_unique": {
+ "name": "session_shares_link_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "link_token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "jwt_token": {
+ "name": "jwt_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_sub": {
+ "name": "oidc_sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oidc_sid": {
+ "name": "oidc_sid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_active_at": {
+ "name": "last_active_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.shared_host_auth_overrides": {
+ "name": "shared_host_auth_overrides",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ssh'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "shared_host_auth_overrides_host_user_protocol_unique": {
+ "name": "shared_host_auth_overrides_host_user_protocol_unique",
+ "columns": [
+ {
+ "expression": "host_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "protocol",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_host_auth_overrides_host_id_ssh_data_id_fk": {
+ "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_user_id_users_id_fk": {
+ "name": "shared_host_auth_overrides_user_id_users_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.shared_host_secrets": {
+ "name": "shared_host_secrets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host_access_id": {
+ "name": "host_access_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ssh'"
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'credential'"
+ },
+ "original_credential_id": {
+ "name": "original_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_username": {
+ "name": "encrypted_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_auth_type": {
+ "name": "encrypted_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_key": {
+ "name": "encrypted_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_key_password": {
+ "name": "encrypted_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_key_type": {
+ "name": "encrypted_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_domain": {
+ "name": "encrypted_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_shared_host_secrets_scope": {
+ "name": "idx_shared_host_secrets_scope",
+ "columns": [
+ {
+ "expression": "host_access_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "protocol",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_host_secrets_host_access_id_host_access_id_fk": {
+ "name": "shared_host_secrets_host_access_id_host_access_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "host_access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_target_user_id_users_id_fk": {
+ "name": "shared_host_secrets_target_user_id_users_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "original_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.snippet_access": {
+ "name": "snippet_access",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snippet_id": {
+ "name": "snippet_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'view'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippet_access_snippet_id_snippets_id_fk": {
+ "name": "snippet_access_snippet_id_snippets_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "snippets",
+ "columnsFrom": [
+ "snippet_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_user_id_users_id_fk": {
+ "name": "snippet_access_user_id_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_role_id_roles_id_fk": {
+ "name": "snippet_access_role_id_roles_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_granted_by_users_id_fk": {
+ "name": "snippet_access_granted_by_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.snippet_folders": {
+ "name": "snippet_folders",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippet_folders_user_id_users_id_fk": {
+ "name": "snippet_folders_user_id_users_id_fk",
+ "tableFrom": "snippet_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "snippet_folders_sync_id_unique": {
+ "name": "snippet_folders_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.snippets": {
+ "name": "snippets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "host_filter": {
+ "name": "host_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippets_user_id_users_id_fk": {
+ "name": "snippets_user_id_users_id_fk",
+ "tableFrom": "snippets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "snippets_sync_id_unique": {
+ "name": "snippets_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh_credential_usage": {
+ "name": "ssh_credential_usage",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_credential_usage_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_host_id_ssh_data_id_fk": {
+ "name": "ssh_credential_usage_host_id_ssh_data_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_user_id_users_id_fk": {
+ "name": "ssh_credential_usage_user_id_users_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh_credentials": {
+ "name": "ssh_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "detected_key_type": {
+ "name": "detected_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cert_public_key": {
+ "name": "cert_public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "usage_count": {
+ "name": "usage_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_credentials_user_id_users_id_fk": {
+ "name": "ssh_credentials_user_id_users_id_fk",
+ "tableFrom": "ssh_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "ssh_credentials_sync_id_unique": {
+ "name": "ssh_credentials_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh_folders": {
+ "name": "ssh_folders",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_folders_user_id_users_id_fk": {
+ "name": "ssh_folders_user_id_users_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_folders_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_folders_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "ssh_folders_sync_id_unique": {
+ "name": "ssh_folders_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sync_tombstones": {
+ "name": "sync_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sync_tombstones_user_id_users_id_fk": {
+ "name": "sync_tombstones_user_id_users_id_fk",
+ "tableFrom": "sync_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.termix_identities": {
+ "name": "termix_identities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identities_user_id_users_id_fk": {
+ "name": "termix_identities_user_id_users_id_fk",
+ "tableFrom": "termix_identities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "termix_identities_user_id_unique": {
+ "name": "termix_identities_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ },
+ "termix_identities_handle_unique": {
+ "name": "termix_identities_handle_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "handle"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.termix_identity_ca": {
+ "name": "termix_identity_ca",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "validity_days": {
+ "name": "validity_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 90
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identity_ca_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_ca_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_ca_user_id_users_id_fk": {
+ "name": "termix_identity_ca_user_id_users_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "termix_identity_ca_identity_id_unique": {
+ "name": "termix_identity_ca_identity_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "identity_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.termix_identity_keys": {
+ "name": "termix_identity_keys",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "algorithm": {
+ "name": "algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'manual'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identity_keys_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_keys_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_user_id_users_id_fk": {
+ "name": "termix_identity_keys_user_id_users_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_credential_id_ssh_credentials_id_fk": {
+ "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tmux_session_tags": {
+ "name": "tmux_session_tags",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_name": {
+ "name": "session_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag": {
+ "name": "tag",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tmux_session_tags_user_id_users_id_fk": {
+ "name": "tmux_session_tags_user_id_users_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tmux_session_tags_host_id_ssh_data_id_fk": {
+ "name": "tmux_session_tags_host_id_ssh_data_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.transfer_recent": {
+ "name": "transfer_recent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_host_id": {
+ "name": "source_host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dest_host_id": {
+ "name": "dest_host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dest_path": {
+ "name": "dest_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dest_path_label": {
+ "name": "dest_path_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "transfer_recent_user_id_users_id_fk": {
+ "name": "transfer_recent_user_id_users_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_source_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_source_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "source_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_dest_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_dest_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "dest_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.trusted_devices": {
+ "name": "trusted_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_fingerprint": {
+ "name": "device_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "trusted_devices_user_id_users_id_fk": {
+ "name": "trusted_devices_user_id_users_id_fk",
+ "tableFrom": "trusted_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_open_tabs": {
+ "name": "user_open_tabs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tab_type": {
+ "name": "tab_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tab_order": {
+ "name": "tab_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "backend_session_id": {
+ "name": "backend_session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_open_tabs_user_id_users_id_fk": {
+ "name": "user_open_tabs_user_id_users_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_open_tabs_host_id_ssh_data_id_fk": {
+ "name": "user_open_tabs_host_id_ssh_data_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_preferences": {
+ "name": "user_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "reopen_tabs_on_login": {
+ "name": "reopen_tabs_on_login",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "font_size": {
+ "name": "font_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accent_color": {
+ "name": "accent_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "language": {
+ "name": "language",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "storage_mode": {
+ "name": "storage_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command_autocomplete": {
+ "name": "command_autocomplete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command_palette_enabled": {
+ "name": "command_palette_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "show_host_tags": {
+ "name": "show_host_tags",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "host_tray_on_click": {
+ "name": "host_tray_on_click",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pin_app_rail": {
+ "name": "pin_app_rail",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expand_app_rail_on_hover": {
+ "name": "expand_app_rail_on_hover",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folders_collapsed": {
+ "name": "folders_collapsed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirm_snippet_execution": {
+ "name": "confirm_snippet_execution",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disable_update_check": {
+ "name": "disable_update_check",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirm_tab_close": {
+ "name": "confirm_tab_close",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hidden_rail_tabs": {
+ "name": "hidden_rail_tabs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "compact_host_view": {
+ "name": "compact_host_view",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status_color_scheme": {
+ "name": "status_color_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_themes": {
+ "name": "custom_themes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_keybindings": {
+ "name": "custom_keybindings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_at": {
+ "name": "granted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_user_roles_user_role": {
+ "name": "idx_user_roles_user_role",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_granted_by_users_id_fk": {
+ "name": "user_roles_granted_by_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_admin": {
+ "name": "is_admin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "is_oidc": {
+ "name": "is_oidc",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "oidc_identifier": {
+ "name": "oidc_identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_url": {
+ "name": "authorization_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_url": {
+ "name": "token_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "identifier_path": {
+ "name": "identifier_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name_path": {
+ "name": "name_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'openid email profile'"
+ },
+ "totp_secret": {
+ "name": "totp_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "totp_enabled": {
+ "name": "totp_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "totp_backup_codes": {
+ "name": "totp_backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registered_at": {
+ "name": "registered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "donation_modal_dismissed": {
+ "name": "donation_modal_dismissed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vault_profiles": {
+ "name": "vault_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vault_addr": {
+ "name": "vault_addr",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "vault_namespace": {
+ "name": "vault_namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oidc_mount": {
+ "name": "oidc_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oidc_role": {
+ "name": "oidc_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_mount": {
+ "name": "ssh_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_role": {
+ "name": "ssh_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "valid_principals": {
+ "name": "valid_principals",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared": {
+ "name": "shared",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "vault_profiles_user_id_users_id_fk": {
+ "name": "vault_profiles_user_id_users_id_fk",
+ "tableFrom": "vault_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "vault_profiles_sync_id_unique": {
+ "name": "vault_profiles_sync_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sync_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vault_tokens": {
+ "name": "vault_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_id": {
+ "name": "profile_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_vault_tokens_user_profile": {
+ "name": "idx_vault_tokens_user_profile",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "profile_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vault_tokens_user_id_users_id_fk": {
+ "name": "vault_tokens_user_id_users_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "vault_tokens_profile_id_vault_profiles_id_fk": {
+ "name": "vault_tokens_profile_id_vault_profiles_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webauthn_credentials": {
+ "name": "webauthn_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_verification": {
+ "name": "user_verification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'preferred'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "webauthn_credentials_user_id_users_id_fk": {
+ "name": "webauthn_credentials_user_id_users_id_fk",
+ "tableFrom": "webauthn_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/postgres/meta/_journal.json b/drizzle/postgres/meta/_journal.json
new file mode 100644
index 00000000..cebde34b
--- /dev/null
+++ b/drizzle/postgres/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1785738871078,
+ "tag": "0000_jazzy_infant_terrible",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/drizzle/sqlite/0000_clever_hercules.sql b/drizzle/sqlite/0000_clever_hercules.sql
new file mode 100644
index 00000000..ab475b07
--- /dev/null
+++ b/drizzle/sqlite/0000_clever_hercules.sql
@@ -0,0 +1,836 @@
+CREATE TABLE `alert_firings` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `rule_id` integer NOT NULL,
+ `host_id` integer NOT NULL,
+ `host_name` text NOT NULL,
+ `fired_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `resolved_at` text,
+ `value` real,
+ `message` text NOT NULL,
+ `severity` text DEFAULT 'warning' NOT NULL,
+ `acknowledged` integer DEFAULT false NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `alert_rule_channels` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `rule_id` integer NOT NULL,
+ `channel_id` integer NOT NULL,
+ FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`channel_id`) REFERENCES `notification_channels`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `alert_rules` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer,
+ `name` text NOT NULL,
+ `enabled` integer DEFAULT true NOT NULL,
+ `trigger_type` text NOT NULL,
+ `threshold_value` real,
+ `threshold_duration_seconds` integer,
+ `cooldown_minutes` integer DEFAULT 15 NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `api_keys` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `token_hash` text NOT NULL,
+ `token_prefix` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text,
+ `last_used_at` text,
+ `is_active` integer DEFAULT true NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `audit_logs` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text,
+ `username` text NOT NULL,
+ `action` text NOT NULL,
+ `resource_type` text NOT NULL,
+ `resource_id` text,
+ `resource_name` text,
+ `details` text,
+ `ip_address` text,
+ `user_agent` text,
+ `success` integer NOT NULL,
+ `error_message` text,
+ `timestamp` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE TABLE `c2s_tunnel_presets` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `config` text NOT NULL,
+ `platform` text,
+ `computer_name` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `command_history` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `command` text NOT NULL,
+ `executed_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `dashboard_service_links` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `label` text NOT NULL,
+ `url` text NOT NULL,
+ `order` integer DEFAULT 0 NOT NULL,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `dashboard_service_links_sync_id_unique` ON `dashboard_service_links` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `dismissed_alerts` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `alert_id` text NOT NULL,
+ `dismissed_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_pinned` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `name` text NOT NULL,
+ `path` text NOT NULL,
+ `pinned_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_recent` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `name` text NOT NULL,
+ `path` text NOT NULL,
+ `last_opened` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `file_manager_shortcuts` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `name` text NOT NULL,
+ `path` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `homepage_items` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `type_id` text NOT NULL,
+ `title` text,
+ `config` text DEFAULT '{}' NOT NULL,
+ `folder_id` integer,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `homepage_items_sync_id_unique` ON `homepage_items` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `homepage_layouts` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `layout` text DEFAULT '{}' NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `homepage_layouts_user_id_unique` ON `homepage_layouts` (`user_id`);--> statement-breakpoint
+CREATE TABLE `host_access` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `host_id` integer NOT NULL,
+ `user_id` text,
+ `role_id` integer,
+ `granted_by` text NOT NULL,
+ `permission_level` text DEFAULT 'connect' NOT NULL,
+ `expires_at` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `last_accessed_at` text,
+ `access_count` integer DEFAULT 0 NOT NULL,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `host_health_checks` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `checks` text NOT NULL,
+ `interval_seconds` integer DEFAULT 300 NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_host_health_checks_user_host` ON `host_health_checks` (`user_id`,`host_id`);--> statement-breakpoint
+CREATE TABLE `host_health_history` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `check_id` text NOT NULL,
+ `ts` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `ok` integer NOT NULL,
+ `latency_ms` integer,
+ `detail` text,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `host_metrics_history` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `host_id` integer NOT NULL,
+ `ts` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `cpu_percent` real,
+ `mem_percent` real,
+ `disk_percent` real,
+ `net_rx_bytes` integer,
+ `net_tx_bytes` integer,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `host_metrics_preferences` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `layout` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_host_metrics_prefs_user_host` ON `host_metrics_preferences` (`user_id`,`host_id`);--> statement-breakpoint
+CREATE TABLE `ssh_data` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `connection_type` text DEFAULT 'ssh' NOT NULL,
+ `name` text,
+ `ip` text NOT NULL,
+ `port` integer NOT NULL,
+ `username` text NOT NULL,
+ `folder` text,
+ `tags` text,
+ `pin` integer DEFAULT false NOT NULL,
+ `auth_type` text NOT NULL,
+ `use_warpgate` integer DEFAULT false NOT NULL,
+ `share_ssh_auth` integer DEFAULT false NOT NULL,
+ `force_keyboard_interactive` text,
+ `password` text,
+ `key` text(8192),
+ `key_password` text,
+ `key_type` text,
+ `sudo_password` text,
+ `autostart_password` text,
+ `autostart_key` text(8192),
+ `autostart_key_password` text,
+ `credential_id` integer,
+ `override_credential_username` integer,
+ `vault_profile_id` integer,
+ `enable_terminal` integer DEFAULT true NOT NULL,
+ `enable_session_logging` integer DEFAULT true NOT NULL,
+ `allow_session_sharing` integer DEFAULT true NOT NULL,
+ `enable_command_history` integer DEFAULT true NOT NULL,
+ `enable_tunnel` integer DEFAULT true NOT NULL,
+ `tunnel_connections` text,
+ `jump_hosts` text,
+ `enable_file_manager` integer DEFAULT true NOT NULL,
+ `scp_legacy` integer DEFAULT false NOT NULL,
+ `enable_docker` integer DEFAULT false NOT NULL,
+ `enable_tmux_monitor` integer DEFAULT false NOT NULL,
+ `show_terminal_in_sidebar` integer DEFAULT true NOT NULL,
+ `show_file_manager_in_sidebar` integer DEFAULT false NOT NULL,
+ `show_tunnel_in_sidebar` integer DEFAULT false NOT NULL,
+ `show_docker_in_sidebar` integer DEFAULT false NOT NULL,
+ `show_server_stats_in_sidebar` integer DEFAULT false NOT NULL,
+ `default_path` text,
+ `stats_config` text,
+ `docker_config` text,
+ `enable_proxmox` integer DEFAULT false NOT NULL,
+ `proxmox_config` text,
+ `terminal_config` text,
+ `quick_actions` text,
+ `notes` text,
+ `enable_ssh` integer DEFAULT true NOT NULL,
+ `enable_rdp` integer DEFAULT false NOT NULL,
+ `enable_vnc` integer DEFAULT false NOT NULL,
+ `enable_telnet` integer DEFAULT false NOT NULL,
+ `ssh_port` integer DEFAULT 22,
+ `rdp_port` integer DEFAULT 3389,
+ `vnc_port` integer DEFAULT 5900,
+ `telnet_port` integer DEFAULT 23,
+ `rdp_credential_id` integer,
+ `rdp_user` text,
+ `rdp_password` text,
+ `rdp_domain` text,
+ `rdp_security` text,
+ `rdp_ignore_cert` integer DEFAULT false,
+ `vnc_credential_id` integer,
+ `vnc_password` text,
+ `vnc_user` text,
+ `telnet_user` text,
+ `telnet_password` text,
+ `telnet_credential_id` integer,
+ `rdp_auth_type` text,
+ `vnc_auth_type` text,
+ `telnet_auth_type` text,
+ `domain` text,
+ `security` text,
+ `ignore_cert` integer DEFAULT false,
+ `guacamole_config` text,
+ `use_socks5` integer,
+ `socks5_host` text,
+ `socks5_port` integer,
+ `socks5_username` text,
+ `socks5_password` text,
+ `socks5_proxy_chain` text,
+ `connection_origin` text,
+ `mac_address` text,
+ `wol_broadcast_address` text,
+ `port_knock_sequence` text,
+ `host_key_fingerprint` text,
+ `host_key_type` text,
+ `host_key_algorithm` text DEFAULT 'sha256',
+ `host_key_first_seen` text,
+ `host_key_last_verified` text,
+ `host_key_changed_count` integer DEFAULT 0,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`vault_profile_id`) REFERENCES `vault_profiles`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`rdp_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`vnc_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`telnet_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `ssh_data_sync_id_unique` ON `ssh_data` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `network_topology` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `topology` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `notification_channels` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `type` text NOT NULL,
+ `config` text NOT NULL,
+ `enabled` integer DEFAULT true NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `opkssh_tokens` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `ssh_cert` text(8192) NOT NULL,
+ `private_key` text(8192) NOT NULL,
+ `email` text,
+ `sub` text,
+ `issuer` text,
+ `audience` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text NOT NULL,
+ `last_used` text,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_opkssh_tokens_user_host` ON `opkssh_tokens` (`user_id`,`host_id`);--> statement-breakpoint
+CREATE TABLE `recent_activity` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `type` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `host_name` text,
+ `timestamp` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `roles` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `display_name` text NOT NULL,
+ `description` text,
+ `is_system` integer DEFAULT false NOT NULL,
+ `permissions` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `roles_name_unique` ON `roles` (`name`);--> statement-breakpoint
+CREATE TABLE `session_recordings` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `host_id` integer NOT NULL,
+ `user_id` text,
+ `username` text,
+ `access_id` integer,
+ `started_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `ended_at` text,
+ `duration` integer,
+ `commands` text,
+ `dangerous_actions` text,
+ `recording_path` text,
+ `protocol` text DEFAULT 'ssh' NOT NULL,
+ `format` text DEFAULT 'text' NOT NULL,
+ `terminated_by_owner` integer DEFAULT false,
+ `termination_reason` text,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`access_id`) REFERENCES `host_access`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE TABLE `session_share_participants` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `share_id` text NOT NULL,
+ `user_id` text,
+ `guest_label` text,
+ `joined_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `left_at` text,
+ FOREIGN KEY (`share_id`) REFERENCES `session_shares`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `session_shares` (
+ `id` text PRIMARY KEY NOT NULL,
+ `host_id` integer NOT NULL,
+ `owner_user_id` text NOT NULL,
+ `protocol` text NOT NULL,
+ `session_id` text NOT NULL,
+ `tab_instance_id` text,
+ `share_type` text NOT NULL,
+ `target_user_id` text,
+ `link_token` text,
+ `permission_level` text DEFAULT 'read-only' NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text NOT NULL,
+ `revoked_at` text,
+ `last_joined_at` text,
+ `join_count` integer DEFAULT 0 NOT NULL,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`owner_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `session_shares_link_token_unique` ON `session_shares` (`link_token`);--> statement-breakpoint
+CREATE TABLE `sessions` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` text NOT NULL,
+ `jwt_token` text NOT NULL,
+ `device_type` text NOT NULL,
+ `device_info` text NOT NULL,
+ `oidc_sub` text,
+ `oidc_sid` text,
+ `sso_provider_id` integer,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text NOT NULL,
+ `last_active_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `settings` (
+ `key` text PRIMARY KEY NOT NULL,
+ `value` text NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE `shared_host_auth_overrides` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `host_id` integer NOT NULL,
+ `user_id` text NOT NULL,
+ `protocol` text DEFAULT 'ssh' NOT NULL,
+ `credential_id` integer NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `shared_host_auth_overrides_host_user_protocol_unique` ON `shared_host_auth_overrides` (`host_id`,`user_id`,`protocol`);--> statement-breakpoint
+CREATE TABLE `shared_host_secrets` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `host_access_id` integer NOT NULL,
+ `target_user_id` text NOT NULL,
+ `protocol` text DEFAULT 'ssh' NOT NULL,
+ `source_type` text DEFAULT 'credential' NOT NULL,
+ `original_credential_id` integer,
+ `encrypted_username` text,
+ `encrypted_auth_type` text,
+ `encrypted_password` text,
+ `encrypted_key` text(16384),
+ `encrypted_key_password` text,
+ `encrypted_key_type` text,
+ `encrypted_domain` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`host_access_id`) REFERENCES `host_access`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`original_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_shared_host_secrets_scope` ON `shared_host_secrets` (`host_access_id`,`target_user_id`,`protocol`);--> statement-breakpoint
+CREATE TABLE `snippet_access` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `snippet_id` integer NOT NULL,
+ `user_id` text,
+ `role_id` integer,
+ `granted_by` text NOT NULL,
+ `permission_level` text DEFAULT 'view' NOT NULL,
+ `expires_at` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`snippet_id`) REFERENCES `snippets`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `snippet_folders` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `color` text,
+ `icon` text,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `snippet_folders_sync_id_unique` ON `snippet_folders` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `snippets` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `content` text NOT NULL,
+ `description` text,
+ `folder` text,
+ `order` integer DEFAULT 0 NOT NULL,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `host_filter` text,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `snippets_sync_id_unique` ON `snippets` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `ssh_credential_usage` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `credential_id` integer NOT NULL,
+ `host_id` integer NOT NULL,
+ `user_id` text NOT NULL,
+ `used_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `ssh_credentials` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `folder` text,
+ `tags` text,
+ `auth_type` text NOT NULL,
+ `username` text,
+ `password` text,
+ `key` text(16384),
+ `private_key` text(16384),
+ `public_key` text(4096),
+ `key_password` text,
+ `key_type` text,
+ `detected_key_type` text,
+ `cert_public_key` text(8192),
+ `usage_count` integer DEFAULT 0 NOT NULL,
+ `last_used` text,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `ssh_credentials_sync_id_unique` ON `ssh_credentials` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `ssh_folders` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `color` text,
+ `icon` text,
+ `credential_id` integer,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `ssh_folders_sync_id_unique` ON `ssh_folders` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `sso_providers` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `type` text NOT NULL,
+ `enabled` integer DEFAULT true NOT NULL,
+ `display_order` integer DEFAULT 0 NOT NULL,
+ `config` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE `sync_tombstones` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `entity_type` text NOT NULL,
+ `sync_id` text NOT NULL,
+ `deleted_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `termix_identities` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `handle` text NOT NULL,
+ `description` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `termix_identities_user_id_unique` ON `termix_identities` (`user_id`);--> statement-breakpoint
+CREATE UNIQUE INDEX `termix_identities_handle_unique` ON `termix_identities` (`handle`);--> statement-breakpoint
+CREATE TABLE `termix_identity_ca` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `identity_id` integer NOT NULL,
+ `user_id` text NOT NULL,
+ `public_key` text(4096) NOT NULL,
+ `private_key` text(8192) NOT NULL,
+ `validity_days` integer DEFAULT 90 NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `termix_identity_ca_identity_id_unique` ON `termix_identity_ca` (`identity_id`);--> statement-breakpoint
+CREATE TABLE `termix_identity_keys` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `identity_id` integer NOT NULL,
+ `user_id` text NOT NULL,
+ `public_key` text(8192) NOT NULL,
+ `key_type` text NOT NULL,
+ `algorithm` text NOT NULL,
+ `label` text,
+ `comment` text,
+ `source` text DEFAULT 'manual' NOT NULL,
+ `credential_id` integer,
+ `enabled` integer DEFAULT true NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE TABLE `tmux_session_tags` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `host_id` integer NOT NULL,
+ `session_name` text NOT NULL,
+ `tag` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `transfer_recent` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `source_host_id` integer NOT NULL,
+ `dest_host_id` integer NOT NULL,
+ `dest_path` text NOT NULL,
+ `dest_path_label` text NOT NULL,
+ `last_used` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`source_host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`dest_host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `trusted_devices` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` text NOT NULL,
+ `device_fingerprint` text NOT NULL,
+ `device_type` text NOT NULL,
+ `device_info` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text NOT NULL,
+ `last_used_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `user_open_tabs` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` text NOT NULL,
+ `tab_type` text NOT NULL,
+ `host_id` integer,
+ `label` text NOT NULL,
+ `tab_order` integer DEFAULT 0 NOT NULL,
+ `backend_session_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `user_preferences` (
+ `user_id` text PRIMARY KEY NOT NULL,
+ `reopen_tabs_on_login` integer DEFAULT false NOT NULL,
+ `theme` text,
+ `font_size` text,
+ `accent_color` text,
+ `language` text,
+ `storage_mode` text,
+ `command_autocomplete` integer,
+ `command_palette_enabled` integer,
+ `show_host_tags` integer,
+ `host_tray_on_click` integer,
+ `pin_app_rail` integer,
+ `expand_app_rail_on_hover` integer,
+ `folders_collapsed` integer,
+ `confirm_snippet_execution` integer,
+ `disable_update_check` integer,
+ `confirm_tab_close` integer,
+ `hidden_rail_tabs` text,
+ `compact_host_view` integer,
+ `status_color_scheme` text,
+ `custom_themes` text,
+ `custom_keybindings` text,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `user_roles` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `role_id` integer NOT NULL,
+ `granted_by` text,
+ `granted_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_user_roles_user_role` ON `user_roles` (`user_id`,`role_id`);--> statement-breakpoint
+CREATE TABLE `users` (
+ `id` text PRIMARY KEY NOT NULL,
+ `username` text NOT NULL,
+ `password_hash` text NOT NULL,
+ `is_admin` integer DEFAULT false NOT NULL,
+ `is_oidc` integer DEFAULT false NOT NULL,
+ `oidc_identifier` text,
+ `sso_provider_id` integer,
+ `client_id` text,
+ `client_secret` text,
+ `issuer_url` text,
+ `authorization_url` text,
+ `token_url` text,
+ `identifier_path` text,
+ `name_path` text,
+ `scopes` text DEFAULT 'openid email profile',
+ `totp_secret` text,
+ `totp_enabled` integer DEFAULT false NOT NULL,
+ `totp_backup_codes` text,
+ `registered_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `donation_modal_dismissed` integer DEFAULT false NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE `vault_profiles` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `folder` text,
+ `tags` text,
+ `vault_addr` text NOT NULL,
+ `vault_namespace` text,
+ `oidc_mount` text,
+ `oidc_role` text,
+ `ssh_mount` text,
+ `ssh_role` text NOT NULL,
+ `valid_principals` text,
+ `key_type` text,
+ `shared` integer DEFAULT false NOT NULL,
+ `sync_id` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `vault_profiles_sync_id_unique` ON `vault_profiles` (`sync_id`);--> statement-breakpoint
+CREATE TABLE `vault_tokens` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` text NOT NULL,
+ `profile_id` integer NOT NULL,
+ `ssh_cert` text(8192) NOT NULL,
+ `private_key` text(8192) NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `expires_at` text NOT NULL,
+ `last_used` text,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`profile_id`) REFERENCES `vault_profiles`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_vault_tokens_user_profile` ON `vault_tokens` (`user_id`,`profile_id`);--> statement-breakpoint
+CREATE TABLE `webauthn_credentials` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` text NOT NULL,
+ `name` text NOT NULL,
+ `credential_id` text NOT NULL,
+ `public_key` text NOT NULL,
+ `counter` integer DEFAULT 0 NOT NULL,
+ `device_type` text,
+ `backed_up` integer DEFAULT false NOT NULL,
+ `transports` text,
+ `user_verification` text DEFAULT 'preferred' NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ `last_used_at` text,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
diff --git a/drizzle/sqlite/meta/0000_snapshot.json b/drizzle/sqlite/meta/0000_snapshot.json
new file mode 100644
index 00000000..305992fd
--- /dev/null
+++ b/drizzle/sqlite/meta/0000_snapshot.json
@@ -0,0 +1,6080 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "426410c9-f34a-47bc-9df6-17748689ad0d",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "tables": {
+ "alert_firings": {
+ "name": "alert_firings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fired_at": {
+ "name": "fired_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "severity": {
+ "name": "severity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'warning'"
+ },
+ "acknowledged": {
+ "name": "acknowledged",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_firings_user_id_users_id_fk": {
+ "name": "alert_firings_user_id_users_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_firings_rule_id_alert_rules_id_fk": {
+ "name": "alert_firings_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_firings",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "alert_rule_channels": {
+ "name": "alert_rule_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rule_channels_rule_id_alert_rules_id_fk": {
+ "name": "alert_rule_channels_rule_id_alert_rules_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "alert_rules",
+ "columnsFrom": [
+ "rule_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rule_channels_channel_id_notification_channels_id_fk": {
+ "name": "alert_rule_channels_channel_id_notification_channels_id_fk",
+ "tableFrom": "alert_rule_channels",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "alert_rules": {
+ "name": "alert_rules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "threshold_value": {
+ "name": "threshold_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "threshold_duration_seconds": {
+ "name": "threshold_duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cooldown_minutes": {
+ "name": "cooldown_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 15
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "alert_rules_user_id_users_id_fk": {
+ "name": "alert_rules_user_id_users_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "alert_rules_host_id_ssh_data_id_fk": {
+ "name": "alert_rules_host_id_ssh_data_id_fk",
+ "tableFrom": "alert_rules",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "api_keys": {
+ "name": "api_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_keys_user_id_users_id_fk": {
+ "name": "api_keys_user_id_users_id_fk",
+ "tableFrom": "api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "success": {
+ "name": "success",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "c2s_tunnel_presets": {
+ "name": "c2s_tunnel_presets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "computer_name": {
+ "name": "computer_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "c2s_tunnel_presets_user_id_users_id_fk": {
+ "name": "c2s_tunnel_presets_user_id_users_id_fk",
+ "tableFrom": "c2s_tunnel_presets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "command_history": {
+ "name": "command_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "executed_at": {
+ "name": "executed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "command_history_user_id_users_id_fk": {
+ "name": "command_history_user_id_users_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "command_history_host_id_ssh_data_id_fk": {
+ "name": "command_history_host_id_ssh_data_id_fk",
+ "tableFrom": "command_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "dashboard_service_links": {
+ "name": "dashboard_service_links",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "dashboard_service_links_sync_id_unique": {
+ "name": "dashboard_service_links_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "dashboard_service_links_user_id_users_id_fk": {
+ "name": "dashboard_service_links_user_id_users_id_fk",
+ "tableFrom": "dashboard_service_links",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "dismissed_alerts": {
+ "name": "dismissed_alerts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "alert_id": {
+ "name": "alert_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dismissed_at": {
+ "name": "dismissed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dismissed_alerts_user_id_users_id_fk": {
+ "name": "dismissed_alerts_user_id_users_id_fk",
+ "tableFrom": "dismissed_alerts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "file_manager_pinned": {
+ "name": "file_manager_pinned",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_pinned_user_id_users_id_fk": {
+ "name": "file_manager_pinned_user_id_users_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_pinned_host_id_ssh_data_id_fk": {
+ "name": "file_manager_pinned_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_pinned",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "file_manager_recent": {
+ "name": "file_manager_recent",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_opened": {
+ "name": "last_opened",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_recent_user_id_users_id_fk": {
+ "name": "file_manager_recent_user_id_users_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_recent_host_id_ssh_data_id_fk": {
+ "name": "file_manager_recent_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "file_manager_shortcuts": {
+ "name": "file_manager_shortcuts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "file_manager_shortcuts_user_id_users_id_fk": {
+ "name": "file_manager_shortcuts_user_id_users_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "file_manager_shortcuts_host_id_ssh_data_id_fk": {
+ "name": "file_manager_shortcuts_host_id_ssh_data_id_fk",
+ "tableFrom": "file_manager_shortcuts",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "homepage_items": {
+ "name": "homepage_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type_id": {
+ "name": "type_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{}'"
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "homepage_items_sync_id_unique": {
+ "name": "homepage_items_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "homepage_items_user_id_users_id_fk": {
+ "name": "homepage_items_user_id_users_id_fk",
+ "tableFrom": "homepage_items",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "homepage_layouts": {
+ "name": "homepage_layouts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "homepage_layouts_user_id_unique": {
+ "name": "homepage_layouts_user_id_unique",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "homepage_layouts_user_id_users_id_fk": {
+ "name": "homepage_layouts_user_id_users_id_fk",
+ "tableFrom": "homepage_layouts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_access": {
+ "name": "host_access",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'connect'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "last_accessed_at": {
+ "name": "last_accessed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_count": {
+ "name": "access_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_access_host_id_ssh_data_id_fk": {
+ "name": "host_access_host_id_ssh_data_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_user_id_users_id_fk": {
+ "name": "host_access_user_id_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_role_id_roles_id_fk": {
+ "name": "host_access_role_id_roles_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_access_granted_by_users_id_fk": {
+ "name": "host_access_granted_by_users_id_fk",
+ "tableFrom": "host_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_health_checks": {
+ "name": "host_health_checks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "checks": {
+ "name": "checks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 300
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_host_health_checks_user_host": {
+ "name": "idx_host_health_checks_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "host_health_checks_user_id_users_id_fk": {
+ "name": "host_health_checks_user_id_users_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_checks_host_id_ssh_data_id_fk": {
+ "name": "host_health_checks_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_checks",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_health_history": {
+ "name": "host_health_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "check_id": {
+ "name": "check_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "ok": {
+ "name": "ok",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_health_history_user_id_users_id_fk": {
+ "name": "host_health_history_user_id_users_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_health_history_host_id_ssh_data_id_fk": {
+ "name": "host_health_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_health_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics_history": {
+ "name": "host_metrics_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ts": {
+ "name": "ts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "mem_percent": {
+ "name": "mem_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "disk_percent": {
+ "name": "disk_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "net_rx_bytes": {
+ "name": "net_rx_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "net_tx_bytes": {
+ "name": "net_tx_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "host_metrics_history_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_history_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_history",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics_preferences": {
+ "name": "host_metrics_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "layout": {
+ "name": "layout",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_host_metrics_prefs_user_host": {
+ "name": "idx_host_metrics_prefs_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_preferences_user_id_users_id_fk": {
+ "name": "host_metrics_preferences_user_id_users_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "host_metrics_preferences_host_id_ssh_data_id_fk": {
+ "name": "host_metrics_preferences_host_id_ssh_data_id_fk",
+ "tableFrom": "host_metrics_preferences",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ssh_data": {
+ "name": "ssh_data",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip": {
+ "name": "ip",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pin": {
+ "name": "pin",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "use_warpgate": {
+ "name": "use_warpgate",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "share_ssh_auth": {
+ "name": "share_ssh_auth",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "force_keyboard_interactive": {
+ "name": "force_keyboard_interactive",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sudo_password": {
+ "name": "sudo_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_password": {
+ "name": "autostart_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_key": {
+ "name": "autostart_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autostart_key_password": {
+ "name": "autostart_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "override_credential_username": {
+ "name": "override_credential_username",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vault_profile_id": {
+ "name": "vault_profile_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_terminal": {
+ "name": "enable_terminal",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_session_logging": {
+ "name": "enable_session_logging",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "allow_session_sharing": {
+ "name": "allow_session_sharing",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_command_history": {
+ "name": "enable_command_history",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_tunnel": {
+ "name": "enable_tunnel",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "tunnel_connections": {
+ "name": "tunnel_connections",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "jump_hosts": {
+ "name": "jump_hosts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_file_manager": {
+ "name": "enable_file_manager",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "scp_legacy": {
+ "name": "scp_legacy",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_docker": {
+ "name": "enable_docker",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_tmux_monitor": {
+ "name": "enable_tmux_monitor",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_terminal_in_sidebar": {
+ "name": "show_terminal_in_sidebar",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "show_file_manager_in_sidebar": {
+ "name": "show_file_manager_in_sidebar",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_tunnel_in_sidebar": {
+ "name": "show_tunnel_in_sidebar",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_docker_in_sidebar": {
+ "name": "show_docker_in_sidebar",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_server_stats_in_sidebar": {
+ "name": "show_server_stats_in_sidebar",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_path": {
+ "name": "default_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stats_config": {
+ "name": "stats_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "docker_config": {
+ "name": "docker_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_proxmox": {
+ "name": "enable_proxmox",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "proxmox_config": {
+ "name": "proxmox_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "terminal_config": {
+ "name": "terminal_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "quick_actions": {
+ "name": "quick_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enable_ssh": {
+ "name": "enable_ssh",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "enable_rdp": {
+ "name": "enable_rdp",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_vnc": {
+ "name": "enable_vnc",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "enable_telnet": {
+ "name": "enable_telnet",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "ssh_port": {
+ "name": "ssh_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 22
+ },
+ "rdp_port": {
+ "name": "rdp_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 3389
+ },
+ "vnc_port": {
+ "name": "vnc_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 5900
+ },
+ "telnet_port": {
+ "name": "telnet_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 23
+ },
+ "rdp_credential_id": {
+ "name": "rdp_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_user": {
+ "name": "rdp_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_password": {
+ "name": "rdp_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_domain": {
+ "name": "rdp_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_security": {
+ "name": "rdp_security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_ignore_cert": {
+ "name": "rdp_ignore_cert",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "vnc_credential_id": {
+ "name": "vnc_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_password": {
+ "name": "vnc_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_user": {
+ "name": "vnc_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_user": {
+ "name": "telnet_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_password": {
+ "name": "telnet_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_credential_id": {
+ "name": "telnet_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rdp_auth_type": {
+ "name": "rdp_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vnc_auth_type": {
+ "name": "vnc_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telnet_auth_type": {
+ "name": "telnet_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "security": {
+ "name": "security",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ignore_cert": {
+ "name": "ignore_cert",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "guacamole_config": {
+ "name": "guacamole_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "use_socks5": {
+ "name": "use_socks5",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_host": {
+ "name": "socks5_host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_port": {
+ "name": "socks5_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_username": {
+ "name": "socks5_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_password": {
+ "name": "socks5_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "socks5_proxy_chain": {
+ "name": "socks5_proxy_chain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_origin": {
+ "name": "connection_origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "mac_address": {
+ "name": "mac_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "wol_broadcast_address": {
+ "name": "wol_broadcast_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port_knock_sequence": {
+ "name": "port_knock_sequence",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_fingerprint": {
+ "name": "host_key_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_type": {
+ "name": "host_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_algorithm": {
+ "name": "host_key_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'sha256'"
+ },
+ "host_key_first_seen": {
+ "name": "host_key_first_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_last_verified": {
+ "name": "host_key_last_verified",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_key_changed_count": {
+ "name": "host_key_changed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "ssh_data_sync_id_unique": {
+ "name": "ssh_data_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "ssh_data_user_id_users_id_fk": {
+ "name": "ssh_data_user_id_users_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_data_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vault_profile_id_vault_profiles_id_fk": {
+ "name": "ssh_data_vault_profile_id_vault_profiles_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "vault_profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_rdp_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "rdp_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_vnc_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "vnc_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ssh_data_telnet_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_data",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "telnet_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "network_topology": {
+ "name": "network_topology",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "topology": {
+ "name": "topology",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "network_topology_user_id_users_id_fk": {
+ "name": "network_topology_user_id_users_id_fk",
+ "tableFrom": "network_topology",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_channels": {
+ "name": "notification_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_channels_user_id_users_id_fk": {
+ "name": "notification_channels_user_id_users_id_fk",
+ "tableFrom": "notification_channels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "opkssh_tokens": {
+ "name": "opkssh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sub": {
+ "name": "sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_opkssh_tokens_user_host": {
+ "name": "idx_opkssh_tokens_user_host",
+ "columns": [
+ "user_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "opkssh_tokens_user_id_users_id_fk": {
+ "name": "opkssh_tokens_user_id_users_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "opkssh_tokens_host_id_ssh_data_id_fk": {
+ "name": "opkssh_tokens_host_id_ssh_data_id_fk",
+ "tableFrom": "opkssh_tokens",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "recent_activity": {
+ "name": "recent_activity",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recent_activity_user_id_users_id_fk": {
+ "name": "recent_activity_user_id_users_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "recent_activity_host_id_ssh_data_id_fk": {
+ "name": "recent_activity_host_id_ssh_data_id_fk",
+ "tableFrom": "recent_activity",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session_recordings": {
+ "name": "session_recordings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_id": {
+ "name": "access_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "commands": {
+ "name": "commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dangerous_actions": {
+ "name": "dangerous_actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "recording_path": {
+ "name": "recording_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "format": {
+ "name": "format",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'text'"
+ },
+ "terminated_by_owner": {
+ "name": "terminated_by_owner",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "termination_reason": {
+ "name": "termination_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_recordings_host_id_ssh_data_id_fk": {
+ "name": "session_recordings_host_id_ssh_data_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_recordings_user_id_users_id_fk": {
+ "name": "session_recordings_user_id_users_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "session_recordings_access_id_host_access_id_fk": {
+ "name": "session_recordings_access_id_host_access_id_fk",
+ "tableFrom": "session_recordings",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session_share_participants": {
+ "name": "session_share_participants",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "share_id": {
+ "name": "share_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "guest_label": {
+ "name": "guest_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "left_at": {
+ "name": "left_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_share_participants_share_id_session_shares_id_fk": {
+ "name": "session_share_participants_share_id_session_shares_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "session_shares",
+ "columnsFrom": [
+ "share_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_share_participants_user_id_users_id_fk": {
+ "name": "session_share_participants_user_id_users_id_fk",
+ "tableFrom": "session_share_participants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session_shares": {
+ "name": "session_shares",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_instance_id": {
+ "name": "tab_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "share_type": {
+ "name": "share_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "link_token": {
+ "name": "link_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'read-only'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_joined_at": {
+ "name": "last_joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "join_count": {
+ "name": "join_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "session_shares_link_token_unique": {
+ "name": "session_shares_link_token_unique",
+ "columns": [
+ "link_token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "session_shares_host_id_ssh_data_id_fk": {
+ "name": "session_shares_host_id_ssh_data_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_owner_user_id_users_id_fk": {
+ "name": "session_shares_owner_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_shares_target_user_id_users_id_fk": {
+ "name": "session_shares_target_user_id_users_id_fk",
+ "tableFrom": "session_shares",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "jwt_token": {
+ "name": "jwt_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "oidc_sub": {
+ "name": "oidc_sub",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_sid": {
+ "name": "oidc_sid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_active_at": {
+ "name": "last_active_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "shared_host_auth_overrides": {
+ "name": "shared_host_auth_overrides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "shared_host_auth_overrides_host_user_protocol_unique": {
+ "name": "shared_host_auth_overrides_host_user_protocol_unique",
+ "columns": [
+ "host_id",
+ "user_id",
+ "protocol"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "shared_host_auth_overrides_host_id_ssh_data_id_fk": {
+ "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_user_id_users_id_fk": {
+ "name": "shared_host_auth_overrides_user_id_users_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_auth_overrides",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "shared_host_secrets": {
+ "name": "shared_host_secrets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "host_access_id": {
+ "name": "host_access_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_user_id": {
+ "name": "target_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ssh'"
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'credential'"
+ },
+ "original_credential_id": {
+ "name": "original_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_username": {
+ "name": "encrypted_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_auth_type": {
+ "name": "encrypted_auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key": {
+ "name": "encrypted_key",
+ "type": "text(16384)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key_password": {
+ "name": "encrypted_key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_key_type": {
+ "name": "encrypted_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "encrypted_domain": {
+ "name": "encrypted_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_shared_host_secrets_scope": {
+ "name": "idx_shared_host_secrets_scope",
+ "columns": [
+ "host_access_id",
+ "target_user_id",
+ "protocol"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "shared_host_secrets_host_access_id_host_access_id_fk": {
+ "name": "shared_host_secrets_host_access_id_host_access_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "host_access",
+ "columnsFrom": [
+ "host_access_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_target_user_id_users_id_fk": {
+ "name": "shared_host_secrets_target_user_id_users_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "target_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": {
+ "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "shared_host_secrets",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "original_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "snippet_access": {
+ "name": "snippet_access",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "snippet_id": {
+ "name": "snippet_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_level": {
+ "name": "permission_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'view'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "snippet_access_snippet_id_snippets_id_fk": {
+ "name": "snippet_access_snippet_id_snippets_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "snippets",
+ "columnsFrom": [
+ "snippet_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_user_id_users_id_fk": {
+ "name": "snippet_access_user_id_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_role_id_roles_id_fk": {
+ "name": "snippet_access_role_id_roles_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "snippet_access_granted_by_users_id_fk": {
+ "name": "snippet_access_granted_by_users_id_fk",
+ "tableFrom": "snippet_access",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "snippet_folders": {
+ "name": "snippet_folders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "snippet_folders_sync_id_unique": {
+ "name": "snippet_folders_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "snippet_folders_user_id_users_id_fk": {
+ "name": "snippet_folders_user_id_users_id_fk",
+ "tableFrom": "snippet_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "snippets": {
+ "name": "snippets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "host_filter": {
+ "name": "host_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "snippets_sync_id_unique": {
+ "name": "snippets_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "snippets_user_id_users_id_fk": {
+ "name": "snippets_user_id_users_id_fk",
+ "tableFrom": "snippets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ssh_credential_usage": {
+ "name": "ssh_credential_usage",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh_credential_usage_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_host_id_ssh_data_id_fk": {
+ "name": "ssh_credential_usage_host_id_ssh_data_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_credential_usage_user_id_users_id_fk": {
+ "name": "ssh_credential_usage_user_id_users_id_fk",
+ "tableFrom": "ssh_credential_usage",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ssh_credentials": {
+ "name": "ssh_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text(16384)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text(16384)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text(4096)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_password": {
+ "name": "key_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "detected_key_type": {
+ "name": "detected_key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cert_public_key": {
+ "name": "cert_public_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "usage_count": {
+ "name": "usage_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "ssh_credentials_sync_id_unique": {
+ "name": "ssh_credentials_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "ssh_credentials_user_id_users_id_fk": {
+ "name": "ssh_credentials_user_id_users_id_fk",
+ "tableFrom": "ssh_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ssh_folders": {
+ "name": "ssh_folders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "ssh_folders_sync_id_unique": {
+ "name": "ssh_folders_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "ssh_folders_user_id_users_id_fk": {
+ "name": "ssh_folders_user_id_users_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ssh_folders_credential_id_ssh_credentials_id_fk": {
+ "name": "ssh_folders_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "ssh_folders",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sso_providers": {
+ "name": "sso_providers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sync_tombstones": {
+ "name": "sync_tombstones",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sync_tombstones_user_id_users_id_fk": {
+ "name": "sync_tombstones_user_id_users_id_fk",
+ "tableFrom": "sync_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "termix_identities": {
+ "name": "termix_identities",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "termix_identities_user_id_unique": {
+ "name": "termix_identities_user_id_unique",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": true
+ },
+ "termix_identities_handle_unique": {
+ "name": "termix_identities_handle_unique",
+ "columns": [
+ "handle"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "termix_identities_user_id_users_id_fk": {
+ "name": "termix_identities_user_id_users_id_fk",
+ "tableFrom": "termix_identities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "termix_identity_ca": {
+ "name": "termix_identity_ca",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text(4096)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "validity_days": {
+ "name": "validity_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 90
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "termix_identity_ca_identity_id_unique": {
+ "name": "termix_identity_ca_identity_id_unique",
+ "columns": [
+ "identity_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "termix_identity_ca_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_ca_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_ca_user_id_users_id_fk": {
+ "name": "termix_identity_ca_user_id_users_id_fk",
+ "tableFrom": "termix_identity_ca",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "termix_identity_keys": {
+ "name": "termix_identity_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "identity_id": {
+ "name": "identity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "algorithm": {
+ "name": "algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'manual'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "termix_identity_keys_identity_id_termix_identities_id_fk": {
+ "name": "termix_identity_keys_identity_id_termix_identities_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "termix_identities",
+ "columnsFrom": [
+ "identity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_user_id_users_id_fk": {
+ "name": "termix_identity_keys_user_id_users_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "termix_identity_keys_credential_id_ssh_credentials_id_fk": {
+ "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk",
+ "tableFrom": "termix_identity_keys",
+ "tableTo": "ssh_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tmux_session_tags": {
+ "name": "tmux_session_tags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "session_name": {
+ "name": "session_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tag": {
+ "name": "tag",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tmux_session_tags_user_id_users_id_fk": {
+ "name": "tmux_session_tags_user_id_users_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tmux_session_tags_host_id_ssh_data_id_fk": {
+ "name": "tmux_session_tags_host_id_ssh_data_id_fk",
+ "tableFrom": "tmux_session_tags",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "transfer_recent": {
+ "name": "transfer_recent",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_host_id": {
+ "name": "source_host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_host_id": {
+ "name": "dest_host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_path": {
+ "name": "dest_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dest_path_label": {
+ "name": "dest_path_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "transfer_recent_user_id_users_id_fk": {
+ "name": "transfer_recent_user_id_users_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_source_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_source_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "source_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transfer_recent_dest_host_id_ssh_data_id_fk": {
+ "name": "transfer_recent_dest_host_id_ssh_data_id_fk",
+ "tableFrom": "transfer_recent",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "dest_host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "trusted_devices": {
+ "name": "trusted_devices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_fingerprint": {
+ "name": "device_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_info": {
+ "name": "device_info",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "trusted_devices_user_id_users_id_fk": {
+ "name": "trusted_devices_user_id_users_id_fk",
+ "tableFrom": "trusted_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_open_tabs": {
+ "name": "user_open_tabs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_type": {
+ "name": "tab_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tab_order": {
+ "name": "tab_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "backend_session_id": {
+ "name": "backend_session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_open_tabs_user_id_users_id_fk": {
+ "name": "user_open_tabs_user_id_users_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_open_tabs_host_id_ssh_data_id_fk": {
+ "name": "user_open_tabs_host_id_ssh_data_id_fk",
+ "tableFrom": "user_open_tabs",
+ "tableTo": "ssh_data",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reopen_tabs_on_login": {
+ "name": "reopen_tabs_on_login",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "font_size": {
+ "name": "font_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "accent_color": {
+ "name": "accent_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "language": {
+ "name": "language",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "storage_mode": {
+ "name": "storage_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "command_autocomplete": {
+ "name": "command_autocomplete",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "command_palette_enabled": {
+ "name": "command_palette_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "show_host_tags": {
+ "name": "show_host_tags",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_tray_on_click": {
+ "name": "host_tray_on_click",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pin_app_rail": {
+ "name": "pin_app_rail",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expand_app_rail_on_hover": {
+ "name": "expand_app_rail_on_hover",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folders_collapsed": {
+ "name": "folders_collapsed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "confirm_snippet_execution": {
+ "name": "confirm_snippet_execution",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "disable_update_check": {
+ "name": "disable_update_check",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "confirm_tab_close": {
+ "name": "confirm_tab_close",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hidden_rail_tabs": {
+ "name": "hidden_rail_tabs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compact_host_view": {
+ "name": "compact_host_view",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status_color_scheme": {
+ "name": "status_color_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "custom_themes": {
+ "name": "custom_themes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "custom_keybindings": {
+ "name": "custom_keybindings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "granted_at": {
+ "name": "granted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "idx_user_roles_user_role": {
+ "name": "idx_user_roles_user_role",
+ "columns": [
+ "user_id",
+ "role_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_granted_by_users_id_fk": {
+ "name": "user_roles_granted_by_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "granted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_admin": {
+ "name": "is_admin",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_oidc": {
+ "name": "is_oidc",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "oidc_identifier": {
+ "name": "oidc_identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sso_provider_id": {
+ "name": "sso_provider_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "authorization_url": {
+ "name": "authorization_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_url": {
+ "name": "token_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "identifier_path": {
+ "name": "identifier_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name_path": {
+ "name": "name_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'openid email profile'"
+ },
+ "totp_secret": {
+ "name": "totp_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totp_enabled": {
+ "name": "totp_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "totp_backup_codes": {
+ "name": "totp_backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "registered_at": {
+ "name": "registered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "donation_modal_dismissed": {
+ "name": "donation_modal_dismissed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vault_profiles": {
+ "name": "vault_profiles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "folder": {
+ "name": "folder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vault_addr": {
+ "name": "vault_addr",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "vault_namespace": {
+ "name": "vault_namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_mount": {
+ "name": "oidc_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "oidc_role": {
+ "name": "oidc_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_mount": {
+ "name": "ssh_mount",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_role": {
+ "name": "ssh_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "valid_principals": {
+ "name": "valid_principals",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key_type": {
+ "name": "key_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "shared": {
+ "name": "shared",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "sync_id": {
+ "name": "sync_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "vault_profiles_sync_id_unique": {
+ "name": "vault_profiles_sync_id_unique",
+ "columns": [
+ "sync_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "vault_profiles_user_id_users_id_fk": {
+ "name": "vault_profiles_user_id_users_id_fk",
+ "tableFrom": "vault_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vault_tokens": {
+ "name": "vault_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "profile_id": {
+ "name": "profile_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ssh_cert": {
+ "name": "ssh_cert",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text(8192)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_vault_tokens_user_profile": {
+ "name": "idx_vault_tokens_user_profile",
+ "columns": [
+ "user_id",
+ "profile_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "vault_tokens_user_id_users_id_fk": {
+ "name": "vault_tokens_user_id_users_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "vault_tokens_profile_id_vault_profiles_id_fk": {
+ "name": "vault_tokens_profile_id_vault_profiles_id_fk",
+ "tableFrom": "vault_tokens",
+ "tableTo": "vault_profiles",
+ "columnsFrom": [
+ "profile_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webauthn_credentials": {
+ "name": "webauthn_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_verification": {
+ "name": "user_verification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'preferred'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "webauthn_credentials_user_id_users_id_fk": {
+ "name": "webauthn_credentials_user_id_users_id_fk",
+ "tableFrom": "webauthn_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json
new file mode 100644
index 00000000..3ee197c3
--- /dev/null
+++ b/drizzle/sqlite/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1785738870735,
+ "tag": "0000_clever_hercules",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/electron/backend-paths.cjs b/electron/backend-paths.cjs
new file mode 100644
index 00000000..b99791ab
--- /dev/null
+++ b/electron/backend-paths.cjs
@@ -0,0 +1,8 @@
+function getUnpackedAppRoot(appRoot) {
+ return appRoot.replace(
+ /app(-[a-z0-9]+)?\.asar(?!\.unpacked)/,
+ "app$1.asar.unpacked",
+ );
+}
+
+module.exports = { getUnpackedAppRoot };
diff --git a/electron/main.cjs b/electron/main.cjs
index 2e20608e..6e9c4082 100644
--- a/electron/main.cjs
+++ b/electron/main.cjs
@@ -12,6 +12,7 @@ const {
nativeImage,
} = require("electron");
const path = require("path");
+const { getUnpackedAppRoot } = require("./backend-paths.cjs");
const fs = require("fs");
const os = require("os");
const https = require("https");
@@ -800,10 +801,7 @@ function getBackendPaths() {
// fork() does not go through Electron's asar redirector — use the unpacked path.
// On macOS multi-arch builds (mergeASARs: false), electron-builder names the ASAR
// app-arm64.asar / app-x64.asar instead of app.asar, so match all variants.
- const unpackedRoot = appRoot.replace(
- /app(-[a-z0-9]+)?\.asar(?!\.unpacked)/,
- "app.asar.unpacked",
- );
+ const unpackedRoot = getUnpackedAppRoot(appRoot);
const backendDir = path.join(unpackedRoot, "dist", "backend", "backend");
return {
entryPath: path.join(backendDir, "starter.js"),
@@ -1581,6 +1579,10 @@ ipcMain.handle("get-remote-sync-status", () => {
return remoteSync.getRemoteSyncEngine()?.status || null;
});
+ipcMain.handle("get-remote-sync-user-info", () => {
+ return remoteSync.getRemoteSyncUserInfo();
+});
+
ipcMain.handle("remote-sync-now", async () => {
return (await remoteSync.getRemoteSyncEngine()?.syncNow()) || null;
});
diff --git a/electron/remote-sync-entities.cjs b/electron/remote-sync-entities.cjs
new file mode 100644
index 00000000..a60ce8c4
--- /dev/null
+++ b/electron/remote-sync-entities.cjs
@@ -0,0 +1,15 @@
+const SYNCED_ENTITY_TYPES = Object.freeze([
+ // Ordered by reference dependency: hosts and snippets resolve credential,
+ // vault and folder syncIds, so those have to exist on the other side first.
+ "sshCredentials",
+ "vaultProfiles",
+ "sshFolders",
+ "snippetFolders",
+ "hosts",
+ "snippets",
+ "dashboardServiceLinks",
+ "homepageItems",
+ "userPreferences",
+]);
+
+module.exports = { SYNCED_ENTITY_TYPES };
diff --git a/electron/remote-sync.cjs b/electron/remote-sync.cjs
index b34dd037..26ed9aca 100644
--- a/electron/remote-sync.cjs
+++ b/electron/remote-sync.cjs
@@ -14,17 +14,7 @@
const { app, safeStorage } = require("electron");
const fs = require("fs");
const path = require("path");
-
-const SYNCED_ENTITY_TYPES = [
- "hosts",
- "sshCredentials",
- "sshFolders",
- "snippets",
- "snippetFolders",
- "vaultProfiles",
- "dashboardServiceLinks",
- "homepageItems",
-];
+const { SYNCED_ENTITY_TYPES } = require("./remote-sync-entities.cjs");
const SYNC_INTERVAL_MS = 90 * 1000;
const EMBEDDED_BASE_URL = "http://127.0.0.1:30001";
@@ -135,6 +125,41 @@ function clearRemoteSyncJwt() {
return { success: true };
}
+async function getRemoteSyncUserInfo() {
+ const config = getRemoteSyncConfig();
+ const token = getRemoteSyncJwt();
+ if (!config?.serverUrl || !token || isJwtExpiredOrExpiringSoon(token)) {
+ return null;
+ }
+
+ const baseUrl = config.serverUrl.replace(/\/$/, "");
+ const userResponse = await fetch(`${baseUrl}/users/me`, {
+ headers: { Authorization: `Bearer ${token}`, "X-Electron-App": "true" },
+ });
+ if (!userResponse.ok) return null;
+
+ const user = await userResponse.json();
+ const rolesResponse = await fetch(
+ `${baseUrl}/rbac/users/${encodeURIComponent(user.userId)}/roles`,
+ {
+ headers: { Authorization: `Bearer ${token}`, "X-Electron-App": "true" },
+ },
+ );
+ const roles = rolesResponse.ok
+ ? (await rolesResponse.json()).roles || []
+ : [];
+
+ return {
+ userId: user.userId,
+ username: user.username,
+ is_admin: !!user.is_admin,
+ is_oidc: !!user.is_oidc,
+ is_dual_auth: !!user.is_dual_auth,
+ totp_enabled: !!user.totp_enabled,
+ roles,
+ };
+}
+
function decodeJwtExpiry(token) {
try {
const payloadB64 = token.split(".")[1];
@@ -373,6 +398,15 @@ class RemoteSyncEngine {
return data.rows || [];
}
+ /**
+ * Every syncId a side currently holds, ignoring the incremental window.
+ * Used only to decide whether a deletion still has something to delete.
+ */
+ async pullSyncIds(baseUrl, token, entityType) {
+ const rows = await this.pullSide(baseUrl, token, entityType, null);
+ return new Set(rows.filter((row) => row.syncId).map((row) => row.syncId));
+ }
+
async pullTombstones(baseUrl, token, entityType, since) {
const url = `${baseUrl}/sync/${entityType}/tombstones${since ? `?since=${encodeURIComponent(since)}` : ""}`;
const data = await this.fetchJson(url, token);
@@ -457,24 +491,46 @@ class RemoteSyncEngine {
}
// Apply tombstones to whichever side hasn't already deleted the row.
- for (const tombstone of localTombstones) {
- if (remoteBySyncId.has(tombstone.syncId)) {
- await this.pushTombstone(
- remoteBaseUrl,
- remoteJwt,
- entityType,
- tombstone.syncId,
- );
+ //
+ // The presence check cannot use localRows/remoteRows: those are the
+ // incremental window, and a row deleted on one side while untouched on
+ // the other is by definition outside it, so every deletion was dropped.
+ // It also cannot be skipped -- pushing unconditionally makes the
+ // receiving side record a fresh tombstone, which the next pass would push
+ // back, forever. So ask the receiving side what it actually still holds,
+ // and only when there is a deletion to apply.
+ if (localTombstones.length) {
+ const remoteSyncIds = await this.pullSyncIds(
+ remoteBaseUrl,
+ remoteJwt,
+ entityType,
+ );
+ for (const tombstone of localTombstones) {
+ if (remoteSyncIds.has(tombstone.syncId)) {
+ await this.pushTombstone(
+ remoteBaseUrl,
+ remoteJwt,
+ entityType,
+ tombstone.syncId,
+ );
+ }
}
}
- for (const tombstone of remoteTombstones) {
- if (localBySyncId.has(tombstone.syncId)) {
- await this.pushTombstone(
- EMBEDDED_BASE_URL,
- this.localJwt,
- entityType,
- tombstone.syncId,
- );
+ if (remoteTombstones.length) {
+ const localSyncIds = await this.pullSyncIds(
+ EMBEDDED_BASE_URL,
+ this.localJwt,
+ entityType,
+ );
+ for (const tombstone of remoteTombstones) {
+ if (localSyncIds.has(tombstone.syncId)) {
+ await this.pushTombstone(
+ EMBEDDED_BASE_URL,
+ this.localJwt,
+ entityType,
+ tombstone.syncId,
+ );
+ }
}
}
@@ -511,6 +567,7 @@ module.exports = {
saveRemoteSyncJwt,
getRemoteSyncJwt,
clearRemoteSyncJwt,
+ getRemoteSyncUserInfo,
isJwtExpiredOrExpiringSoon,
decodeJwtExpiry,
};
diff --git a/eslint.config.mjs b/eslint.config.mjs
index ee87445c..78798841 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -46,4 +46,57 @@ export default tseslint.config([
"react-refresh/only-export-components": "warn",
},
},
+ {
+ // MySQL has no RETURNING clause, and drizzle's mysql-core does not expose
+ // the method at all — a bare .returning() is a TypeError there, not a bad
+ // query, and it only fails on the engine no test in this repo runs against.
+ //
+ // 175 call sites were migrated off it. This is what stops number 176.
+ // Writes that need rows back go through repositories/returning.ts, which
+ // picks one statement or a read-then-write transaction per dialect.
+ files: ["src/backend/database/repositories/**/*.ts"],
+ ignores: [
+ // The two files whose job is to absorb these differences.
+ "src/backend/database/repositories/returning.ts",
+ "src/backend/database/repositories/mutation-result.ts",
+ ],
+ rules: {
+ "no-restricted-syntax": [
+ "error",
+ {
+ selector: "CallExpression[callee.property.name='returning']",
+ message:
+ "MySQL has no RETURNING. Use insertReturning/updateReturning/deleteReturning from ./returning.js, or rowsAffected() if you only need a count. Inside a proven sqlite-only branch, disable this rule with a comment saying so.",
+ },
+ {
+ // `||` concatenates on SQLite and Postgres. On MySQL it is logical OR
+ // unless the server runs with PIPES_AS_CONCAT, so a folder path built
+ // this way silently became 0. Use CONCAT, which all three agree on.
+ selector:
+ "TaggedTemplateExpression[tag.name='sql'] TemplateElement[value.raw=/\\|\\|/]",
+ message:
+ "`||` is logical OR on MySQL, not concatenation. Use CONCAT(...).",
+ },
+ {
+ // Postgres and SQLite spell it ON CONFLICT; MySQL spells it ON
+ // DUPLICATE KEY and names no columns, so drizzle's mysql-core has no
+ // onConflictDoUpdate at all — another TypeError, not a bad query.
+ selector: "CallExpression[callee.property.name='onConflictDoUpdate']",
+ message:
+ "MySQL has no ON CONFLICT. Use upsert() from ./returning.js.",
+ },
+ {
+ // better-sqlite3 puts these on a write result; node-postgres and
+ // mysql2 do not, so reading them directly yields undefined — and
+ // Number(undefined) is NaN, which reaches the database as the string
+ // "NaN" and fails an integer column. Three call sites did exactly
+ // this and only broke on Postgres.
+ selector:
+ "MemberExpression[property.name=/^(lastInsertRowid|changes)$/]",
+ message:
+ "lastInsertRowid and changes are better-sqlite3 only. Use insertedId() or rowsAffected() from ./mutation-result.js.",
+ },
+ ],
+ },
+ },
]);
diff --git a/package-lock.json b/package-lock.json
index 691a37fb..f6fc24b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,72 +11,74 @@
"dependencies": {
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.2",
- "@tanstack/react-virtual": "^3.14.6",
+ "@tanstack/react-virtual": "^3.14.9",
"@types/ldapjs": "^3.0.6",
- "axios": "^1.18.1",
+ "axios": "^1.19.0",
"bcryptjs": "^3.0.3",
- "better-sqlite3": "^12.11.1",
+ "better-sqlite3": "^13.0.2",
"body-parser": "^2.3.0",
- "chalk": "^5.6.2",
+ "chalk": "^6.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"drizzle-orm": "^0.45.2",
"express": "^5.2.1",
"guacamole-lite": "^1.2.0",
- "jose": "^6.2.2",
- "js-yaml": "^5.2.1",
+ "jose": "^6.2.5",
+ "js-yaml": "^5.2.2",
"jsonwebtoken": "^9.0.3",
"jszip": "^3.10.1",
"ldapjs": "^3.0.7",
- "motion": "^12.42.2",
+ "motion": "^12.43.0",
"multer": "^2.2.0",
+ "mysql2": "^3.23.2",
"nanoid": "^6.0.0",
+ "pg": "^8.22.0",
"qrcode": "^1.5.4",
"serialport": "^13.0.0",
"socks": "^2.8.7",
"speakeasy": "^2.0.0",
"ssh2": "^1.17.0",
- "undici": "^8.7.0",
+ "undici": "^8.9.0",
"ws": "^8.21.1"
},
"devDependencies": {
- "@biomejs/biome": "2.5.4",
+ "@biomejs/biome": "2.5.6",
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/commands": "^6.10.4",
"@codemirror/search": "^6.7.1",
"@codemirror/theme-one-dark": "^6.1.3",
- "@codemirror/view": "^6.43.6",
- "@commitlint/cli": "^21.0.2",
- "@commitlint/config-conventional": "^21.0.2",
+ "@codemirror/view": "^6.43.7",
+ "@commitlint/cli": "^21.2.1",
+ "@commitlint/config-conventional": "^21.2.0",
"@deadendjs/swagger-jsdoc": "^8.1.2",
"@electron/notarize": "^3.1.1",
- "@electron/rebuild": "^4.0.4",
+ "@electron/rebuild": "^4.2.0",
"@eslint/js": "^10.0.1",
- "@fontsource-variable/jetbrains-mono": "^5.2.8",
- "@fontsource/fira-code": "^5.2.7",
- "@fontsource/jetbrains-mono": "^5.2.8",
- "@fontsource/source-code-pro": "^5.2.7",
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
+ "@fontsource/fira-code": "^5.3.0",
+ "@fontsource/jetbrains-mono": "^5.3.0",
+ "@fontsource/source-code-pro": "^5.3.0",
"@monaco-editor/react": "^4.7.0",
- "@radix-ui/react-accordion": "^1.2.17",
- "@radix-ui/react-alert-dialog": "^1.1.20",
- "@radix-ui/react-checkbox": "^1.3.8",
- "@radix-ui/react-dialog": "^1.1.20",
- "@radix-ui/react-dropdown-menu": "^2.1.21",
- "@radix-ui/react-label": "^2.1.12",
- "@radix-ui/react-popover": "^1.1.20",
- "@radix-ui/react-progress": "^1.1.13",
- "@radix-ui/react-scroll-area": "^1.2.15",
- "@radix-ui/react-select": "^2.3.4",
- "@radix-ui/react-separator": "^1.1.12",
- "@radix-ui/react-slider": "^1.4.4",
- "@radix-ui/react-slot": "^1.3.0",
- "@radix-ui/react-switch": "^1.3.4",
- "@radix-ui/react-tabs": "^1.1.18",
- "@radix-ui/react-tooltip": "^1.2.13",
+ "@radix-ui/react-accordion": "^1.2.20",
+ "@radix-ui/react-alert-dialog": "^1.1.23",
+ "@radix-ui/react-checkbox": "^1.3.11",
+ "@radix-ui/react-dialog": "^1.1.23",
+ "@radix-ui/react-dropdown-menu": "^2.1.24",
+ "@radix-ui/react-label": "^2.1.15",
+ "@radix-ui/react-popover": "^1.1.23",
+ "@radix-ui/react-progress": "^1.1.16",
+ "@radix-ui/react-scroll-area": "^1.2.18",
+ "@radix-ui/react-select": "^2.3.7",
+ "@radix-ui/react-separator": "^1.1.15",
+ "@radix-ui/react-slider": "^1.4.7",
+ "@radix-ui/react-slot": "^1.3.3",
+ "@radix-ui/react-switch": "^1.3.7",
+ "@radix-ui/react-tabs": "^1.1.21",
+ "@radix-ui/react-tooltip": "^1.2.16",
"@tailwindcss/vite": "^4.3.3",
"@testing-library/dom": "^10.4.1",
- "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/better-sqlite3": "^7.6.13",
@@ -86,52 +88,55 @@
"@types/guacamole-common-js": "^1.5.5",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.10",
- "@types/multer": "^2.1.0",
- "@types/node": "^26.0.0",
+ "@types/multer": "^2.2.0",
+ "@types/node": "^26.1.2",
+ "@types/pg": "^8.20.0",
"@types/qrcode": "^1.5.6",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.4",
"@types/speakeasy": "^2.0.10",
"@types/ssh2": "^1.15.5",
"@types/ws": "^8.18.1",
"@uiw/codemirror-extensions-langs": "^4.25.11",
"@uiw/codemirror-theme-github": "^4.25.11",
"@uiw/react-codemirror": "^4.25.11",
- "@vitejs/plugin-react": "^6.0.3",
+ "@vitejs/plugin-react": "^6.0.5",
"@vitest/coverage-v8": "^4.1.10",
"@vitest/ui": "^4.1.10",
"@xterm/addon-clipboard": "^0.2.0",
"@xterm/addon-fit": "^0.11.0",
+ "@xterm/addon-search": "^0.16.0",
"@xterm/addon-unicode11": "^0.9.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
- "concurrently": "^10.0.3",
+ "concurrently": "^10.0.4",
"cytoscape": "^3.34.0",
- "electron": "^43.0.0",
+ "drizzle-kit": "^0.31.10",
+ "electron": "^43.2.0",
"electron-builder": "^26.15.3",
- "eslint": "^10.5.0",
+ "eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"eslint-plugin-unused-imports": "^4.4.1",
- "globals": "^17.5.0",
+ "globals": "^17.8.0",
"guacamole-common-js": "^1.5.0",
"husky": "^9.1.7",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"jsdom": "^29.1.1",
- "lint-staged": "^17.0.8",
- "lucide-react": "^1.20.0",
- "prettier": "3.8.4",
- "radix-ui": "^1.6.3",
- "react": "^19.2.7",
+ "lint-staged": "^17.2.0",
+ "lucide-react": "^1.28.0",
+ "prettier": "3.9.6",
+ "radix-ui": "^1.6.7",
+ "react": "^19.2.8",
"react-cytoscapejs": "^2.0.0",
- "react-dom": "^19.2.7",
+ "react-dom": "^19.2.8",
"react-h5-audio-player": "^3.10.2",
"react-hook-form": "^7.79.0",
- "react-i18next": "^17.0.10",
+ "react-i18next": "^17.0.11",
"react-icons": "^5.6.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.4.1",
@@ -569,9 +574,9 @@
}
},
"node_modules/@biomejs/biome": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.4.tgz",
- "integrity": "sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.6.tgz",
+ "integrity": "sha512-lxVNjv7UF6KfhMJfL9gaUHbWdJdHbsAj6OSmwSYNdhRuG67NxNQ4Xdvh3TUxsSK9sBzJBQhEJj3AopmmNJ5pSA==",
"dev": true,
"license": "MIT OR Apache-2.0",
"bin": {
@@ -585,20 +590,20 @@
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
- "@biomejs/cli-darwin-arm64": "2.5.4",
- "@biomejs/cli-darwin-x64": "2.5.4",
- "@biomejs/cli-linux-arm64": "2.5.4",
- "@biomejs/cli-linux-arm64-musl": "2.5.4",
- "@biomejs/cli-linux-x64": "2.5.4",
- "@biomejs/cli-linux-x64-musl": "2.5.4",
- "@biomejs/cli-win32-arm64": "2.5.4",
- "@biomejs/cli-win32-x64": "2.5.4"
+ "@biomejs/cli-darwin-arm64": "2.5.6",
+ "@biomejs/cli-darwin-x64": "2.5.6",
+ "@biomejs/cli-linux-arm64": "2.5.6",
+ "@biomejs/cli-linux-arm64-musl": "2.5.6",
+ "@biomejs/cli-linux-x64": "2.5.6",
+ "@biomejs/cli-linux-x64-musl": "2.5.6",
+ "@biomejs/cli-win32-arm64": "2.5.6",
+ "@biomejs/cli-win32-x64": "2.5.6"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.4.tgz",
- "integrity": "sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-zMOLZP4oMrjh6m1zcSj1ud2awUPgTuMVbmQhYYWL7J8HwCnbHHBvTm7VBTRuY7epT5bez76IpKYQ11ZAqHFlnw==",
"cpu": [
"arm64"
],
@@ -613,9 +618,9 @@
}
},
"node_modules/@biomejs/cli-darwin-x64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.4.tgz",
- "integrity": "sha512-D32P5HkU2Y6PySuC/WsVDTOgsDwVFmujzhhhOQjajtATpVWFDXuVd3oRbsWNSEA+aaFzyzZm22szsyydBYlSyQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-JAC1VqzvO7Th5ZplU0G2uGfkZbxEe9uDDektPAhF0JLusoz1w+T4okp2bkykI0bbaO2vslKiRfj4gU43JaGreA==",
"cpu": [
"x64"
],
@@ -630,13 +635,16 @@
}
},
"node_modules/@biomejs/cli-linux-arm64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.4.tgz",
- "integrity": "sha512-pSEfW7B8kTsXUjUxC1xVVK+y85Ht3C5XxZ9gclmC7/3Ku9Vqz8jmI7k0p/BNIjQ6t4sFERI2sFeH73ybiZl6YQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.6.tgz",
+ "integrity": "sha512-6XsYwCFkp5sMxl85ffhgeGpGgs6A7dRYFnkceZ7WVxvycuTnGdD5xa534Z3xfrBQ0JCMK/mujT6ZNPJoghedwg==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
@@ -647,13 +655,16 @@
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.4.tgz",
- "integrity": "sha512-Rpm5/AT1m+DlJmUoYvS4/vXc+0tXJPJ2NQz25TGPyHVF5JrWy75PE0GH6kVxsKtQDuCH4OgzquZq0R4kj/wCVg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-eUa3jeeYvfMt19LBeh6E5PUZpxnTC4JqNWo+EDjTtQjAr2xLGnWaxACtVU1DQqmHYbvThlJzLX+ZsYgrqh2qVw==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
@@ -664,13 +675,16 @@
}
},
"node_modules/@biomejs/cli-linux-x64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.4.tgz",
- "integrity": "sha512-FNxojWJkL7EajAuzBgoLe0T2G0y112M4lBrDIFl/DomFTx8yqenYOIdsRLNXvOvBBofE8hJi85LjzLmBDpY7/Q==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.6.tgz",
+ "integrity": "sha512-Pop9VXCFUhFTMfFefZ39S+u2rOPyNp5iHlxbZRwXGACHLy2r0jjiRgJHmaEKJzL3SyxlVeGShXhvvElvWowonA==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
@@ -681,13 +695,16 @@
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.4.tgz",
- "integrity": "sha512-aby/PohmmgbShcHqFsZVzG8H6D98+P+A6xRWRrQcLW1pCjabcov5UUlke4UqNQBYTkDQav+jB4zyyDDeKB2GaA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-2Vp13QdKysH3HIWLaYLhUUwbK+jbZonJD1K+Lr0d0RO4wH7mkYd43vJixEDm8cUWrowoRz4UUHF1nm9Ae7ym8A==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
@@ -698,9 +715,9 @@
}
},
"node_modules/@biomejs/cli-win32-arm64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.4.tgz",
- "integrity": "sha512-emoXexPZIPAZkz2RKmA95WJUqK3I5MJNYtwEbL5ESciRzhmFMMyekDhNG8hpeOaK+ZGRDxAU4wvGuA5IHQ0h0w==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-tDGshcm6BdkZOCGnTDX0Y8/U4IfBSlnUU7T56nNDuPEfed+aHg+u8G36NB43fJVl0Os6+QURXIE1yuD7AaEofA==",
"cpu": [
"arm64"
],
@@ -715,9 +732,9 @@
}
},
"node_modules/@biomejs/cli-win32-x64": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.4.tgz",
- "integrity": "sha512-U1jaluLw1qQc2Tx7/CeSoL9N5XcqIH+GWjpUAy1ouB5nVjSCMNO+NNHdY3RAs8zxNurLWAdj6pehQdCA2zyU+Q==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-WN05KwXnTO/2J45RQPvzZMXf7tZUIofHoR35xIPfCo7pQ2RFidxI8sfb5mGsaTxdMmEOzHzOPRCdA5/fCpc7xQ==",
"cpu": [
"x64"
],
@@ -1129,9 +1146,9 @@
}
},
"node_modules/@codemirror/view": {
- "version": "6.43.6",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz",
- "integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==",
+ "version": "6.43.7",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.7.tgz",
+ "integrity": "sha512-FZsExxkoxnAN+d9TgqXLg5g4A1oQwzX9WlkOT5i2PKkcW7xx3Bmu0vs90g6fo9Mpdsb/l96dnAraQ8932aO4/g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1142,17 +1159,18 @@
}
},
"node_modules/@commitlint/cli": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.2.tgz",
- "integrity": "sha512-YMmfLbqBg+ZRvvmPhc+cilSQFrh/AgzVgCT1U/OifmUZEwPbvCtA8rN//YNaF9d5eoZphxVMGYtmwA2QgQORgg==",
+ "version": "21.2.1",
+ "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.1.tgz",
+ "integrity": "sha512-blsZGe29hJ72VGEFVl72IVYX+1vsfINpjA9yWQA6i7OKD/McGEOXg08sKIRKjFk4JvzhV/9n0l3i6NooPLTNfg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/format": "^21.0.1",
- "@commitlint/lint": "^21.0.2",
- "@commitlint/load": "^21.0.2",
- "@commitlint/read": "^21.0.2",
- "@commitlint/types": "^21.0.1",
+ "@commitlint/config-conventional": "^21.2.0",
+ "@commitlint/format": "^21.2.0",
+ "@commitlint/lint": "^21.2.0",
+ "@commitlint/load": "^21.2.0",
+ "@commitlint/read": "^21.2.1",
+ "@commitlint/types": "^21.2.0",
"tinyexec": "^1.0.0",
"yargs": "^18.0.0"
},
@@ -1263,27 +1281,27 @@
}
},
"node_modules/@commitlint/config-conventional": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.2.tgz",
- "integrity": "sha512-P/ZRhryQmkj0Z0dY9FOoRwe3xkwJyyAdtXwt01NT2kuZttcG2CNYp1q5Ci3u+nDT2jcbJRw2kt13Czl1qKNPfg==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.0.tgz",
+ "integrity": "sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
- "conventional-changelog-conventionalcommits": "^9.2.0"
+ "@commitlint/types": "^21.2.0",
+ "conventional-changelog-conventionalcommits": "^10.0.0"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/@commitlint/config-validator": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz",
- "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz",
+ "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
+ "@commitlint/types": "^21.2.0",
"ajv": "^8.11.0"
},
"engines": {
@@ -1291,13 +1309,13 @@
}
},
"node_modules/@commitlint/ensure": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz",
- "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz",
+ "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
+ "@commitlint/types": "^21.2.0",
"es-toolkit": "^1.46.0"
},
"engines": {
@@ -1315,13 +1333,13 @@
}
},
"node_modules/@commitlint/format": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz",
- "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.0.tgz",
+ "integrity": "sha512-c4q64xaav2U83t7k7RyzJerBZurPer7FxUOY0RL5L/6CZijZ7K+s6HIBGIghj0ey1P2+seRX0J9XQYtDued6tg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
+ "@commitlint/types": "^21.2.0",
"picocolors": "^1.1.1"
},
"engines": {
@@ -1329,13 +1347,13 @@
}
},
"node_modules/@commitlint/is-ignored": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.2.tgz",
- "integrity": "sha512-H5z4t8PC9tUsmZ/o+EptM3Nq8sTFtskAShdcqxCoyzklW5eaVT5xbrDAET2uypzir9Vsj4ZZmBtyKjYe2XqgeQ==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.0.tgz",
+ "integrity": "sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
+ "@commitlint/types": "^21.2.0",
"semver": "^7.6.0"
},
"engines": {
@@ -1343,32 +1361,32 @@
}
},
"node_modules/@commitlint/lint": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.2.tgz",
- "integrity": "sha512-PnUmLYGeGLfW8oVatR9KpNxSHYAnJOEWlMZzfdeFOUq6WUrFx1fGQaWCWJqMoIll/xPM+GdfJV+tKHZVHhl0Fg==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.0.tgz",
+ "integrity": "sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/is-ignored": "^21.0.2",
- "@commitlint/parse": "^21.0.2",
- "@commitlint/rules": "^21.0.2",
- "@commitlint/types": "^21.0.1"
+ "@commitlint/is-ignored": "^21.2.0",
+ "@commitlint/parse": "^21.2.0",
+ "@commitlint/rules": "^21.2.0",
+ "@commitlint/types": "^21.2.0"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/@commitlint/load": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.2.tgz",
- "integrity": "sha512-lwUE70hN0/qE/ZRROhbaX65ly/FF12DrqfReLCESo37M0OQCFAf2jRS+2tSCSORq+bm4Kdju7qNDj46uc1QzTA==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.0.tgz",
+ "integrity": "sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/config-validator": "^21.0.1",
+ "@commitlint/config-validator": "^21.2.0",
"@commitlint/execute-rule": "^21.0.1",
- "@commitlint/resolve-extends": "^21.0.1",
- "@commitlint/types": "^21.0.1",
+ "@commitlint/resolve-extends": "^21.2.0",
+ "@commitlint/types": "^21.2.0",
"cosmiconfig": "^9.0.1",
"cosmiconfig-typescript-loader": "^6.1.0",
"es-toolkit": "^1.46.0",
@@ -1380,9 +1398,9 @@
}
},
"node_modules/@commitlint/message": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.2.tgz",
- "integrity": "sha512-5n4aqHGD/FNnom/D5L8i7cYtV+xjuXcBL832C3w9VglEsZzIsoHpJsvxzJ7cgiOsOdc/2jU4t5+7qMHh7GBX3g==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz",
+ "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1390,30 +1408,30 @@
}
},
"node_modules/@commitlint/parse": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.2.tgz",
- "integrity": "sha512-QVZJhGHTm+oiuWyEKOCTQ0ZM3mfJ0eGWFeHuj7WzSKEth+UukcCHac9GD8pgdFlg/qGkFWOtyaNd1T8REgagaw==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.0.tgz",
+ "integrity": "sha512-QHWxG4d0PLTF634/AdyZ0MQS+CLn5YOuJlCFhMMlSGKFxzYGUetkHBj18xgBD+6fVzUrA2lrCdi/vlS2f/oYXg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/types": "^21.0.1",
- "conventional-changelog-angular": "^8.2.0",
- "conventional-commits-parser": "^6.3.0"
+ "@commitlint/types": "^21.2.0",
+ "conventional-changelog-angular": "^9.0.0",
+ "conventional-commits-parser": "^7.0.0"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/@commitlint/read": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.2.tgz",
- "integrity": "sha512-BtsrnLVycSSKf4Q0gMch4giCj5NNlmcbhc8ra5vONgGtP2IjRDo33bEFtr5Pm+2N+5fXGWb2MksWPrspPfdhdw==",
+ "version": "21.2.1",
+ "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz",
+ "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/top-level": "^21.0.2",
- "@commitlint/types": "^21.0.1",
- "git-raw-commits": "^5.0.0",
+ "@commitlint/top-level": "^21.2.0",
+ "@commitlint/types": "^21.2.0",
+ "@conventional-changelog/git-client": "^3.0.0",
"tinyexec": "^1.0.0"
},
"engines": {
@@ -1421,14 +1439,14 @@
}
},
"node_modules/@commitlint/resolve-extends": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz",
- "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.0.tgz",
+ "integrity": "sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/config-validator": "^21.0.1",
- "@commitlint/types": "^21.0.1",
+ "@commitlint/config-validator": "^21.2.0",
+ "@commitlint/types": "^21.2.0",
"es-toolkit": "^1.46.0",
"global-directory": "^5.0.0",
"resolve-from": "^5.0.0"
@@ -1438,16 +1456,16 @@
}
},
"node_modules/@commitlint/rules": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.2.tgz",
- "integrity": "sha512-k6tQ69Td7t2qUSIbik8D3TL1q3ZJpkEbV+yLogDzCRAdOxJm4ndhtBNREsLA1/puRfWvzS9eioF2w43WT+hHgQ==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.0.tgz",
+ "integrity": "sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@commitlint/ensure": "^21.0.1",
- "@commitlint/message": "^21.0.2",
+ "@commitlint/ensure": "^21.2.0",
+ "@commitlint/message": "^21.2.0",
"@commitlint/to-lines": "^21.0.1",
- "@commitlint/types": "^21.0.1"
+ "@commitlint/types": "^21.2.0"
},
"engines": {
"node": ">=22.12.0"
@@ -1464,9 +1482,9 @@
}
},
"node_modules/@commitlint/top-level": {
- "version": "21.0.2",
- "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.2.tgz",
- "integrity": "sha512-s9KKM+e+mXgFeIh4n7KmOGAVT3mkJ3Fp1bBYHIK5pjeUwlEMzp/tZfb5u0Poa680AsQTXMEMRxZi1vQ9m2X5ug==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz",
+ "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1477,13 +1495,13 @@
}
},
"node_modules/@commitlint/types": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz",
- "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==",
+ "version": "21.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz",
+ "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "conventional-commits-parser": "^6.3.0",
+ "conventional-commits-parser": "^7.0.0",
"picocolors": "^1.1.1"
},
"engines": {
@@ -1491,22 +1509,22 @@
}
},
"node_modules/@conventional-changelog/git-client": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz",
- "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.0.tgz",
+ "integrity": "sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@simple-libs/child-process-utils": "^1.0.0",
- "@simple-libs/stream-utils": "^1.2.0",
+ "@simple-libs/child-process-utils": "^2.0.0",
+ "@simple-libs/stream-utils": "^2.0.0",
"semver": "^7.5.2"
},
"engines": {
- "node": ">=18"
+ "node": ">=22"
},
"peerDependencies": {
- "conventional-commits-filter": "^5.0.0",
- "conventional-commits-parser": "^6.4.0"
+ "conventional-commits-filter": "^6.0.1",
+ "conventional-commits-parser": "^7.0.1"
},
"peerDependenciesMeta": {
"conventional-commits-filter": {
@@ -1517,6 +1535,16 @@
}
}
},
+ "node_modules/@conventional-changelog/template": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.2.1.tgz",
+ "integrity": "sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ }
+ },
"node_modules/@csstools/color-helpers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
@@ -1676,6 +1704,13 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@drizzle-team/brocli": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
+ "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@electron-internal/extract-zip": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.3.tgz",
@@ -1888,9 +1923,9 @@
}
},
"node_modules/@electron/rebuild": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz",
- "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz",
+ "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2009,6 +2044,884 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@esbuild-kit/core-utils": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
+ "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
+ "deprecated": "Merged into tsx: https://tsx.hirok.io",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.18.20",
+ "source-map-support": "^0.5.21"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/@esbuild-kit/esm-loader": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
+ "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
+ "deprecated": "Merged into tsx: https://tsx.hirok.io",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@esbuild-kit/core-utils": "^3.3.2",
+ "get-tsconfig": "^4.7.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -2054,9 +2967,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
- "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
+ "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -2185,9 +3098,9 @@
"license": "MIT"
},
"node_modules/@fontsource-variable/jetbrains-mono": {
- "version": "5.2.8",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
- "integrity": "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
+ "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==",
"dev": true,
"license": "OFL-1.1",
"funding": {
@@ -2195,9 +3108,9 @@
}
},
"node_modules/@fontsource/fira-code": {
- "version": "5.2.7",
- "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.2.7.tgz",
- "integrity": "sha512-tnB9NNund9TwIym8/7DMJe573nlPEQb+fKUV5GL8TBYXjIhDvL0D7mgmNVNQUPhXp+R7RylQeiBdkA4EbOHPGQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.3.0.tgz",
+ "integrity": "sha512-EJL968RJRkakubAj/coU8pSUaeTE5UNoRjtzAr6kGiSZ3jWuN8/AKWHwym/PFUaQL1q7IL/H+EXs4358YhrTBQ==",
"dev": true,
"license": "OFL-1.1",
"funding": {
@@ -2205,9 +3118,9 @@
}
},
"node_modules/@fontsource/jetbrains-mono": {
- "version": "5.2.8",
- "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
- "integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
+ "integrity": "sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==",
"dev": true,
"license": "OFL-1.1",
"funding": {
@@ -2215,9 +3128,9 @@
}
},
"node_modules/@fontsource/source-code-pro": {
- "version": "5.2.7",
- "resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.7.tgz",
- "integrity": "sha512-7papq9TH94KT+S5VSY8cU7tFmwuGkIe3qxXRMscuAXH6AjMU+KJI75f28FzgBVDrlMfA0jjlTV4/x5+H5o/5EQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.3.0.tgz",
+ "integrity": "sha512-JxaCODU69HDS3mVra9u96nyBF911La6IvtGLgpQD+PZLxJ1i9IxooNfLR6Y37kx06IFjVGkvoUmg3WPwh/8gBg==",
"dev": true,
"license": "OFL-1.1",
"funding": {
@@ -3822,27 +4735,27 @@
"license": "MIT"
},
"node_modules/@radix-ui/number": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz",
- "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz",
+ "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==",
"dev": true,
"license": "MIT"
},
"node_modules/@radix-ui/primitive": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.6.tgz",
- "integrity": "sha512-w9hl+724uYEgCGR3bhuRepjBtrNB/6gkhCnAf58Ke+SLbHPPQqVZZB59z60roB+5H+nh3nWTcdJhQdFMEydWmw==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz",
+ "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@radix-ui/react-accessible-icon": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.12.tgz",
- "integrity": "sha512-Y0zhCQ/XUdTom5hAxvE8RlXqR4hZmKGK6g2//LfgHmb88PJFOpXSh9B/7FlfYXezVY5FKGjRYWCYz5FXxZ9WZQ==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz",
+ "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-visually-hidden": "1.2.8"
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
"peerDependencies": {
"@types/react": "*",
@@ -3860,21 +4773,21 @@
}
},
"node_modules/@radix-ui/react-accordion": {
- "version": "1.2.17",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.17.tgz",
- "integrity": "sha512-l3Dmp+qPPc3SqT8+SPnxIgoWBEU2MMBxcQ7BsoRgak2UT75xY83SFvFcrUkUAWukOV3LFF+BQ9aBIFtZsIG8yQ==",
+ "version": "1.2.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz",
+ "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collapsible": "1.1.17",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collapsible": "1.1.20",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -3892,17 +4805,17 @@
}
},
"node_modules/@radix-ui/react-alert-dialog": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.20.tgz",
- "integrity": "sha512-Ft1W+jPqSh5BKfSTe4dpq6UYQKKQJ5Tvq3wfux+WVlg7nPwFK/3pIlHTb3Rbe+b/tNurx8YGXD9em91ujmgwuQ==",
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz",
+ "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dialog": "1.1.20",
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dialog": "1.1.23",
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -3920,13 +4833,13 @@
}
},
"node_modules/@radix-ui/react-arrow": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.12.tgz",
- "integrity": "sha512-ltXCE0glRomMZ9+u10d9o1Go+edqa1aLxufH59JRNNM3Yz1uvaeNWSaS1HeVh1X64agtdBG5JA1W1I6ySqWiwA==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz",
+ "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -3944,13 +4857,13 @@
}
},
"node_modules/@radix-ui/react-aspect-ratio": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.12.tgz",
- "integrity": "sha512-Sok2IBJxA1XO4pU3ldzZMwUBMumIt64EY8zOUlVq5CdS+i0FrEbajVslfDB+YGWLMsrjY2kZQB0DgkrZXLZvcg==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz",
+ "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -3968,18 +4881,18 @@
}
},
"node_modules/@radix-ui/react-avatar": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.3.tgz",
- "integrity": "sha512-peavtnApRB1tABx42tHw+rPU83GSg5tXicMYO/Xi1/lqNcRsF6jkr6L7Njo7gj4q/xtDRDKBkqJvbMtoOMYWtA==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz",
+ "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-is-hydrated": "0.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -3997,19 +4910,19 @@
}
},
"node_modules/@radix-ui/react-checkbox": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.8.tgz",
- "integrity": "sha512-wfN60IGuxynWK7rP4Ks2p7u9G7gqirzkAiFptuzVbsR1ot2/K+PavNUAtxiKxyRfLOvSbVfvvm9m3rFqLEXz7A==",
+ "version": "1.3.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz",
+ "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-size": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4027,20 +4940,20 @@
}
},
"node_modules/@radix-ui/react-collapsible": {
- "version": "1.1.17",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.17.tgz",
- "integrity": "sha512-DJgqGsNXa0df3ifz9PFNgvgj/bzIu5QTVWCt5nQWaUkM6y0EarUv4QG4s6mCoeQdOIyVOT/Q1osFuEGub2TDXQ==",
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz",
+ "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4058,16 +4971,16 @@
}
},
"node_modules/@radix-ui/react-collection": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz",
- "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz",
+ "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-slot": "1.3.0"
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -4085,9 +4998,9 @@
}
},
"node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz",
- "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz",
+ "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4101,9 +5014,9 @@
}
},
"node_modules/@radix-ui/react-context": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz",
- "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz",
+ "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4117,17 +5030,17 @@
}
},
"node_modules/@radix-ui/react-context-menu": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.4.tgz",
- "integrity": "sha512-eO9tkvHvo4dNwb+lytEcKWjy8c8To+ttLwNt0f9XzzsVFIaspqt3i1/c0JaaksxBB5G//zPo9CCgn39huWQyBA==",
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz",
+ "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-menu": "2.1.21",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -4145,25 +5058,25 @@
}
},
"node_modules/@radix-ui/react-dialog": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.20.tgz",
- "integrity": "sha512-cngVJcvK0yMvR7wICJpv+1uW3Qw4T7QM5sdbb+oE/lxOdTdvF00oaRpWUjVgmjyXe3J+xh7eZyXZlVF3g2g59g==",
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz",
+ "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-focus-guards": "1.1.4",
- "@radix-ui/react-focus-scope": "1.1.13",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.7.2"
},
@@ -4183,9 +5096,9 @@
}
},
"node_modules/@radix-ui/react-direction": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz",
- "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz",
+ "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4199,17 +5112,17 @@
}
},
"node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.16.tgz",
- "integrity": "sha512-t45h68IjFx0ccBnPJqk0X6ecv69LkCFWd6DNCFQX56mUnVEXZbNOLCH/u9fHlAjFZ1RrFdl8/m4zev7B7NyhXQ==",
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz",
+ "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-effect-event": "0.0.3"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-effect-event": "0.0.5"
},
"peerDependencies": {
"@types/react": "*",
@@ -4227,19 +5140,19 @@
}
},
"node_modules/@radix-ui/react-dropdown-menu": {
- "version": "2.1.21",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.21.tgz",
- "integrity": "sha512-gavFM1iWLmWdxWNdGJHVeWeSQul5WE/0pxfvWWt1QnD71hyyujyMCDVacqBomaSOjdxwDzYB+Ng4+MxOvrFB1A==",
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz",
+ "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-menu": "2.1.21",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -4257,9 +5170,9 @@
}
},
"node_modules/@radix-ui/react-focus-guards": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz",
- "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==",
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz",
+ "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4273,15 +5186,15 @@
}
},
"node_modules/@radix-ui/react-focus-scope": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.13.tgz",
- "integrity": "sha512-dE04aPEuP9rvKKT0d0KjSOtTEYNg6bmCYFsoSJpfC+y91Hic28ZfDCGgv6aJ+2Kw/LBXYipMZpyqVj/OD3Z8Gg==",
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz",
+ "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2"
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4299,18 +5212,18 @@
}
},
"node_modules/@radix-ui/react-form": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.13.tgz",
- "integrity": "sha512-PopvWqiutoZh5TJXk9EV9Wh+khbp+LQ+A0H4uHocIjVcKIi6gMlBy4sAaW15thwUSc6PrR8J62nB2uM+htqrcg==",
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz",
+ "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-label": "2.1.12",
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-label": "2.1.15",
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -4328,21 +5241,21 @@
}
},
"node_modules/@radix-ui/react-hover-card": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.20.tgz",
- "integrity": "sha512-UPmdiR8NsngWjG/y9mClzFg+Rbbpy8u0p0SKM+t7mfH4V07TiLsuylqR0RhJiRibopsawoTtMQudm/TxwHWa9w==",
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz",
+ "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -4360,13 +5273,13 @@
}
},
"node_modules/@radix-ui/react-id": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz",
- "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz",
+ "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4379,13 +5292,13 @@
}
},
"node_modules/@radix-ui/react-label": {
- "version": "2.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.12.tgz",
- "integrity": "sha512-dxioNQ7VOrYKKWJIxMRmJPDSWQN0gNCUy3zaqUSBwsuFAiFzI0yLGJr2q3ml07k/HlOk55N8KEfwa1ZgfprJ3w==",
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz",
+ "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -4403,28 +5316,28 @@
}
},
"node_modules/@radix-ui/react-menu": {
- "version": "2.1.21",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.21.tgz",
- "integrity": "sha512-2BHtaJHvvoWTECyrja1mOjN6z2dWdpeHL6b8PxqZYgex8J8xakT2KAchpZIaMwNPauIRHH/VlPJYhSSKe8lz2g==",
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz",
+ "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-focus-guards": "1.1.4",
- "@radix-ui/react-focus-scope": "1.1.13",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.7.2"
},
@@ -4444,22 +5357,22 @@
}
},
"node_modules/@radix-ui/react-menubar": {
- "version": "1.1.21",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.21.tgz",
- "integrity": "sha512-uQONG1qM4D8FSEt0xRs5yDpzeSWggf8lOKqHa84NvqoVoc1qJ6XN+gdkrJuQCyEY55793gLlQI3wjgWO5A/Oqg==",
+ "version": "1.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz",
+ "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-menu": "2.1.21",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -4477,26 +5390,26 @@
}
},
"node_modules/@radix-ui/react-navigation-menu": {
- "version": "1.2.19",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.19.tgz",
- "integrity": "sha512-58OVQUrpWx/zGVV3lxGUyAtjX4n0305Z8xIdUAq2QlFO2m2hd1eBS4x1yIVtV8bzCQJja0TJttWcwiPI6y6tmw==",
+ "version": "1.2.22",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz",
+ "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-use-previous": "1.1.2",
- "@radix-ui/react-visually-hidden": "1.2.8"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
"peerDependencies": {
"@types/react": "*",
@@ -4514,24 +5427,24 @@
}
},
"node_modules/@radix-ui/react-one-time-password-field": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.13.tgz",
- "integrity": "sha512-reLtbZtEBsMcqXkjd/wOga4e8t9uxzFHdX9W/j/ZfGznTNJxLGjRrDNGnGOOWcBazMH1BI/b7Cx+hblSWSD7aw==",
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz",
+ "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/number": "1.1.2",
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-effect-event": "0.0.3",
- "@radix-ui/react-use-is-hydrated": "0.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4549,20 +5462,20 @@
}
},
"node_modules/@radix-ui/react-password-toggle-field": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.8.tgz",
- "integrity": "sha512-NH9puF7Es5Loh8vFELm+SyayzV27nyBw8kiP/uD9wbkwgq359FfbkKEvccrNk75z0LiSqC4REWk1iL9xdeWJkQ==",
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz",
+ "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-effect-event": "0.0.3",
- "@radix-ui/react-use-is-hydrated": "0.1.1"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -4580,25 +5493,25 @@
}
},
"node_modules/@radix-ui/react-popover": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.20.tgz",
- "integrity": "sha512-/PYqbsyuDkNj+IxMcRx71qNt6GelnuNulMwdCV7AtFEhUyK6XkbwreEN6CCLydMeTiDozBV4uv5aF5d12dDH7w==",
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz",
+ "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-focus-guards": "1.1.4",
- "@radix-ui/react-focus-scope": "1.1.13",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-use-controllable-state": "1.2.4",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.7.2"
},
@@ -4618,22 +5531,22 @@
}
},
"node_modules/@radix-ui/react-popper": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.4.tgz",
- "integrity": "sha512-PXnCa3XgTQk0FegMctxgqJXtFLZe4IFJdbUkB7jKSCKEpb6utEO4S9Vog/pkyCfEPdzM331gvE4xpztmBAfMng==",
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz",
+ "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.0.0",
- "@radix-ui/react-arrow": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-use-rect": "1.1.2",
- "@radix-ui/react-use-size": "1.1.2",
- "@radix-ui/rect": "1.1.2"
+ "@radix-ui/react-arrow": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-rect": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4",
+ "@radix-ui/rect": "1.1.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -4651,14 +5564,14 @@
}
},
"node_modules/@radix-ui/react-portal": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.14.tgz",
- "integrity": "sha512-REwjAGPMa3J9oyDE4cuWkZbwnCbbyky66NurquQklXMSDn67cl6oGFx2gO7KZhPtFNbNw9xTWNrti3VIhgluYw==",
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz",
+ "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4676,13 +5589,13 @@
}
},
"node_modules/@radix-ui/react-presence": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.8.tgz",
- "integrity": "sha512-0hhyrQdXMaATgq4ammLG9+iPqsXxzZkgTSIxdrJHdfLnXO4Uo5L7BoO3/Xf0AEaettadGZWGGJMw6ujzQvIpGA==",
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz",
+ "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4700,13 +5613,13 @@
}
},
"node_modules/@radix-ui/react-primitive": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz",
- "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==",
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz",
+ "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-slot": "1.3.0"
+ "@radix-ui/react-slot": "1.3.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -4724,14 +5637,14 @@
}
},
"node_modules/@radix-ui/react-progress": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.13.tgz",
- "integrity": "sha512-1dUdKDd63Tz9FfbTw20MVr28ohG4v7HOJ1dsavGBBPBS3KGzLOyLKiMJAC1OdgiY18nTSHpD4fULGK5gsLY/ww==",
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz",
+ "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -4749,21 +5662,21 @@
}
},
"node_modules/@radix-ui/react-radio-group": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.4.tgz",
- "integrity": "sha512-OpbUmp/korY+tjEQmHwGyQ+QQ3LBlCPC70z03Q/NSqGaHf2EijuwpjQPnswrH6cZLWyT2J6FmB+kzRoMUtPBig==",
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz",
+ "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-size": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4781,23 +5694,23 @@
}
},
"node_modules/@radix-ui/react-roving-focus": {
- "version": "1.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.16.tgz",
- "integrity": "sha512-w7lLsTSd3940vFYEshKkHw+NGf7H0QDJPHYsy8NRjDCVbO6ZdKW1X/xoJSYHZtttnrdZiYqbN2O/2uHGB0zasw==",
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz",
+ "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-is-hydrated": "0.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4815,21 +5728,21 @@
}
},
"node_modules/@radix-ui/react-scroll-area": {
- "version": "1.2.15",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.15.tgz",
- "integrity": "sha512-JVBHNfTBbGd9hhq/xZZOgmVnBCXhLs8PJJ8vMzgwI0pLZNsKckW9pkoqHyxokUCt1hoxbwDNvF9DItEeZsG68g==",
+ "version": "1.2.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz",
+ "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/number": "1.1.2",
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4847,32 +5760,32 @@
}
},
"node_modules/@radix-ui/react-select": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.4.tgz",
- "integrity": "sha512-E2JxqAvaTUEhWtBptWo02g8FnLYPymv9ahEvW/cZQPPV4ySeyo0M8n3sXccsLUAIfMbexnfXt91qF7UjTbTMMg==",
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz",
+ "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/number": "1.1.2",
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-focus-guards": "1.1.4",
- "@radix-ui/react-focus-scope": "1.1.13",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-use-previous": "1.1.2",
- "@radix-ui/react-visually-hidden": "1.2.8",
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.7.2"
},
@@ -4892,13 +5805,13 @@
}
},
"node_modules/@radix-ui/react-separator": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.12.tgz",
- "integrity": "sha512-2hezgFBBR5jU3S9L9bIZ9Uag6LnvxuFBNsLCfTR8qx+NshuvFmpL4C72+5zMS3Z6UgHNSU1thOw2UaBBPEDpsQ==",
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz",
+ "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -4916,23 +5829,23 @@
}
},
"node_modules/@radix-ui/react-slider": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.4.tgz",
- "integrity": "sha512-8dUytW34KoJaB22ctfP7hqUCuyYa8xn2w7H8kCneeOtS5oM7UBivcnZtR8P4kPYMgdoAZlkMhE9/qkYZ5MlRzQ==",
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz",
+ "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/number": "1.1.2",
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-use-previous": "1.1.2",
- "@radix-ui/react-use-size": "1.1.2"
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4950,13 +5863,13 @@
}
},
"node_modules/@radix-ui/react-slot": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz",
- "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz",
+ "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.3"
+ "@radix-ui/react-compose-refs": "1.1.5"
},
"peerDependencies": {
"@types/react": "*",
@@ -4969,18 +5882,18 @@
}
},
"node_modules/@radix-ui/react-switch": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.4.tgz",
- "integrity": "sha512-7iGMj1SfZBAc6xRiy0Y3Wr/v52viQeDhOmaM3fNRyNf2nbYooZA3kKoEDGLPtrDv8JitpzcueqdZLusVMojLdQ==",
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz",
+ "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-size": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -4998,20 +5911,20 @@
}
},
"node_modules/@radix-ui/react-tabs": {
- "version": "1.1.18",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.18.tgz",
- "integrity": "sha512-1zq2XkQkK/KfbZn84edytYpOLquhNalra5LXc3NAMKhNRSGtyXqjMv6OyC9jlSuNKpqvQtsb57WKoICNk1v/sQ==",
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz",
+ "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -5029,24 +5942,24 @@
}
},
"node_modules/@radix-ui/react-toast": {
- "version": "1.2.20",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.20.tgz",
- "integrity": "sha512-S28OtO1IvYSpWfaUBtiYCTTwRLF8doafj+a+uQw8rc8dLINS52uuG3CIPCeZc3Jfdb/S7o7HhlQxLoXlIYRu6g==",
+ "version": "1.2.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz",
+ "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-visually-hidden": "1.2.8"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
"peerDependencies": {
"@types/react": "*",
@@ -5064,15 +5977,15 @@
}
},
"node_modules/@radix-ui/react-toggle": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.15.tgz",
- "integrity": "sha512-tyCejFjhJ51UKFVIG8jh9nTdRIsFPxrgrI4IdlxuJeP+AKTfTko+0gBueyBFLHqsyE71Aj9PKHjMnG+YRPyKhA==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz",
+ "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -5090,19 +6003,19 @@
}
},
"node_modules/@radix-ui/react-toggle-group": {
- "version": "1.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.16.tgz",
- "integrity": "sha512-uil+A0Um3LaZQJkMap4nIg0VgqWc0j3iNU4AXf9a/zHOgPHNYWfVk5WVsG2296Y8HLv1bxiN7uQJblHc1+00tw==",
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz",
+ "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-toggle": "1.1.15",
- "@radix-ui/react-use-controllable-state": "1.2.4"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-toggle": "1.1.18",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
},
"peerDependencies": {
"@types/react": "*",
@@ -5120,19 +6033,19 @@
}
},
"node_modules/@radix-ui/react-toolbar": {
- "version": "1.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.16.tgz",
- "integrity": "sha512-ZnvUAH+ftoRYzUzFQ8gqKnQ1lUFYb3amguGu+BXpfjvLIkjmXCcHCJlQeBLBlJCOtGNVtP+wHrZaUCC/zYKQMg==",
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz",
+ "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-separator": "1.1.12",
- "@radix-ui/react-toggle-group": "1.1.16"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-separator": "1.1.15",
+ "@radix-ui/react-toggle-group": "1.1.19"
},
"peerDependencies": {
"@types/react": "*",
@@ -5150,25 +6063,25 @@
}
},
"node_modules/@radix-ui/react-tooltip": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.13.tgz",
- "integrity": "sha512-56XPNYGMnGBcPyiBTaEXB7IGPybbsdNkFgSv90SCrHkXnu2Av1HhsyZMegzXlTu/QHA3V6/l22GZCv9iEoiqmQ==",
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz",
+ "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-id": "1.1.2",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-visually-hidden": "1.2.8"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
"peerDependencies": {
"@types/react": "*",
@@ -5186,9 +6099,9 @@
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz",
- "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz",
+ "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -5202,15 +6115,15 @@
}
},
"node_modules/@radix-ui/react-use-controllable-state": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.4.tgz",
- "integrity": "sha512-cx2DixxmSfjCcEoRvDvy1NLd6SWK94XFcEEOZUcharUlXbmahFQGKCfwdKZL2ub34iIwOPOEFVF80xb+yfLYiA==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz",
+ "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-use-effect-event": "0.0.3",
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -5223,13 +6136,13 @@
}
},
"node_modules/@radix-ui/react-use-effect-event": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz",
- "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz",
+ "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -5242,13 +6155,13 @@
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.3.tgz",
- "integrity": "sha512-3wEkMiPHXha/2VadZ68rYBcmYnPINVGl4Y3gtcM7fKRjANk0OscK+cdqBgUWdozb7YJxsh0vefM7vgAMHXOjqg==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz",
+ "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.2"
+ "@radix-ui/react-use-callback-ref": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -5261,9 +6174,9 @@
}
},
"node_modules/@radix-ui/react-use-is-hydrated": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz",
- "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==",
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz",
+ "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -5277,9 +6190,9 @@
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz",
- "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz",
+ "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -5293,9 +6206,9 @@
}
},
"node_modules/@radix-ui/react-use-previous": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz",
- "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz",
+ "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -5309,13 +6222,13 @@
}
},
"node_modules/@radix-ui/react-use-rect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz",
- "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz",
+ "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/rect": "1.1.2"
+ "@radix-ui/rect": "1.1.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -5328,13 +6241,13 @@
}
},
"node_modules/@radix-ui/react-use-size": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz",
- "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz",
+ "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.2"
+ "@radix-ui/react-use-layout-effect": "1.1.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -5347,13 +6260,13 @@
}
},
"node_modules/@radix-ui/react-visually-hidden": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.8.tgz",
- "integrity": "sha512-FjsQEpkNBJJYiPSat6jh2LGKLPX2jAoDVS3AZSBNX3cOUoEGhw/f+z2FCY8Cf1NkoYIbytJ1f4mlWPQpR+MjVg==",
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz",
+ "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.7"
+ "@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
@@ -5371,9 +6284,9 @@
}
},
"node_modules/@radix-ui/rect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz",
- "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz",
+ "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==",
"dev": true,
"license": "MIT"
},
@@ -5953,29 +6866,29 @@
}
},
"node_modules/@simple-libs/child-process-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz",
- "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz",
+ "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@simple-libs/stream-utils": "^1.2.0"
+ "@simple-libs/stream-utils": "^2.0.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=22"
},
"funding": {
"url": "https://ko-fi.com/dangreen"
}
},
"node_modules/@simple-libs/stream-utils": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
- "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz",
+ "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=22"
},
"funding": {
"url": "https://ko-fi.com/dangreen"
@@ -6627,12 +7540,12 @@
}
},
"node_modules/@tanstack/react-virtual": {
- "version": "3.14.6",
- "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.6.tgz",
- "integrity": "sha512-4+Uq8m0/gzO4kMCHUEpTtGX1RnONK0C+g88b2ltwPMWUBiaVarBuWKoPJaz7gj1cKCVRAdyu+U8GcKhwCc2beA==",
+ "version": "3.14.9",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz",
+ "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/virtual-core": "3.17.4"
+ "@tanstack/virtual-core": "3.17.7"
},
"funding": {
"type": "github",
@@ -6644,9 +7557,9 @@
}
},
"node_modules/@tanstack/virtual-core": {
- "version": "3.17.4",
- "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.4.tgz",
- "integrity": "sha512-nGm5KteqxasUdThLc2izl6dHUqLv0LQj7Nuyo5gYalTPf/U8a9ermvsl7reT+6ioBW1l8WfpP/mcU338nLXpqw==",
+ "version": "3.17.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz",
+ "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -6691,9 +7604,9 @@
"license": "MIT"
},
"node_modules/@testing-library/jest-dom": {
- "version": "6.9.1",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
- "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz",
+ "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6705,9 +7618,12 @@
"redent": "^3.0.0"
},
"engines": {
- "node": ">=14",
+ "node": ">=22",
"npm": ">=6",
"yarn": ">=1"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=10 <11"
}
},
"node_modules/@testing-library/react": {
@@ -6984,9 +7900,9 @@
"license": "MIT"
},
"node_modules/@types/multer": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
- "integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz",
+ "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6994,14 +7910,26 @@
}
},
"node_modules/@types/node": {
- "version": "26.0.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
- "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
+ "version": "26.1.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
+ "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
}
},
+ "node_modules/@types/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
"node_modules/@types/prismjs": {
"version": "1.26.6",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz",
@@ -7034,9 +7962,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.2.17",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
- "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7044,9 +7972,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -7495,9 +8423,9 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
- "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz",
+ "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7723,6 +8651,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@xterm/addon-search": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz",
+ "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@xterm/addon-unicode11": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0.tgz",
@@ -7841,22 +8776,6 @@
}
}
},
- "node_modules/ansi-escapes": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
- "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "environment": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@@ -8076,6 +8995,19 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
+ "node_modules/argue-cli": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz",
+ "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/dangreen"
+ }
+ },
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -8099,13 +9031,6 @@
"node": ">= 0.4"
}
},
- "node_modules/array-ify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
- "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -8210,6 +9135,15 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
@@ -8218,13 +9152,13 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
- "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
+ "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
- "form-data": "^4.0.5",
+ "form-data": "^4.0.6",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
@@ -8254,20 +9188,6 @@
"node": ">= 6"
}
},
- "node_modules/b4a": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
- "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "react-native-b4a": "*"
- },
- "peerDependenciesMeta": {
- "react-native-b4a": {
- "optional": true
- }
- }
- },
"node_modules/backoff": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
@@ -8301,97 +9221,6 @@
"node": "18 || 20 || >=22"
}
},
- "node_modules/bare-events": {
- "version": "2.8.3",
- "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz",
- "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "bare-abort-controller": "*"
- },
- "peerDependenciesMeta": {
- "bare-abort-controller": {
- "optional": true
- }
- }
- },
- "node_modules/bare-fs": {
- "version": "4.7.1",
- "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz",
- "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==",
- "license": "Apache-2.0",
- "dependencies": {
- "bare-events": "^2.5.4",
- "bare-path": "^3.0.0",
- "bare-stream": "^2.6.4",
- "bare-url": "^2.2.2",
- "fast-fifo": "^1.3.2"
- },
- "engines": {
- "bare": ">=1.16.0"
- },
- "peerDependencies": {
- "bare-buffer": "*"
- },
- "peerDependenciesMeta": {
- "bare-buffer": {
- "optional": true
- }
- }
- },
- "node_modules/bare-os": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz",
- "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==",
- "license": "Apache-2.0",
- "engines": {
- "bare": ">=1.14.0"
- }
- },
- "node_modules/bare-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
- "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
- "license": "Apache-2.0",
- "dependencies": {
- "bare-os": "^3.0.1"
- }
- },
- "node_modules/bare-stream": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz",
- "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==",
- "license": "Apache-2.0",
- "dependencies": {
- "streamx": "^2.25.0",
- "teex": "^1.0.1"
- },
- "peerDependencies": {
- "bare-abort-controller": "*",
- "bare-buffer": "*",
- "bare-events": "*"
- },
- "peerDependenciesMeta": {
- "bare-abort-controller": {
- "optional": true
- },
- "bare-buffer": {
- "optional": true
- },
- "bare-events": {
- "optional": true
- }
- }
- },
- "node_modules/bare-url": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz",
- "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "bare-path": "^3.0.0"
- }
- },
"node_modules/base32.js": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz",
@@ -8451,17 +9280,15 @@
}
},
"node_modules/better-sqlite3": {
- "version": "12.11.1",
- "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
- "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
- "hasInstallScript": true,
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.2.tgz",
+ "integrity": "sha512-jW6oufeDhXZaiX9Lw5A+oerVClx4iFrI6uDj1zu7SqUAjak9vbJvA0NEcKLNxHiQHb6kYCoFzzXYV0YOauhV3g==",
"license": "MIT",
"dependencies": {
- "bindings": "^1.5.0",
- "prebuild-install": "^7.1.1"
+ "node-addon-api": "^8.0.0"
},
"engines": {
- "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
+ "node": ">=22"
}
},
"node_modules/bidi-js": {
@@ -8474,15 +9301,6 @@
"require-from-string": "^2.0.2"
}
},
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "license": "MIT",
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -8820,12 +9638,12 @@
}
},
"node_modules/chalk": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz",
+ "integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==",
"license": "MIT",
"engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ "node": ">=22"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
@@ -8921,22 +9739,6 @@
"url": "https://polar.sh/cva"
}
},
- "node_modules/cli-cursor": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
- "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "restore-cursor": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -9099,17 +9901,6 @@
"node": ">=20"
}
},
- "node_modules/compare-func": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
- "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-ify": "^1.0.0",
- "dot-prop": "^5.1.0"
- }
- },
"node_modules/compare-version": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
@@ -9157,15 +9948,15 @@
}
},
"node_modules/concurrently": {
- "version": "10.0.3",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz",
- "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==",
+ "version": "10.0.4",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.4.tgz",
+ "integrity": "sha512-trZql+7l/0+WRAsAnEdctr4+iiOS6ZrViI6H8QWcCF9MFS/LT0dKpe8vluB1to6it+OxSI4VospFTIFMW8DJRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "5.6.2",
"rxjs": "7.8.2",
- "shell-quote": "1.8.4",
+ "shell-quote": "1.9.0",
"supports-color": "10.2.2",
"tree-kill": "1.2.2",
"yargs": "18.0.0"
@@ -9194,6 +9985,19 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/concurrently/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/concurrently/node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
@@ -9303,46 +10107,46 @@
}
},
"node_modules/conventional-changelog-angular": {
- "version": "8.3.1",
- "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz",
- "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==",
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.2.1.tgz",
+ "integrity": "sha512-oWSL6ZhnXbYraOFTK3PgRAQJ8fADDAEv5K6AdeyQPLvjFmhG8+ejL0jZZp/R7vTmGJaBvZEE+sE7dB4bCv7sAw==",
"dev": true,
"license": "ISC",
"dependencies": {
- "compare-func": "^2.0.0"
+ "@conventional-changelog/template": "^1.2.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=22"
}
},
"node_modules/conventional-changelog-conventionalcommits": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz",
- "integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==",
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.2.1.tgz",
+ "integrity": "sha512-n4Kr1HFMTf3iMbES0TMxKIcYtUUv4rKqyQQp2JwfOEfFCOfGT3Tq4mCyJ8S9/YPyWhydjfKrrvnyl+gCjA+mJQ==",
"dev": true,
"license": "ISC",
"dependencies": {
- "compare-func": "^2.0.0"
+ "@conventional-changelog/template": "^1.2.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=22"
}
},
"node_modules/conventional-commits-parser": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz",
- "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz",
+ "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@simple-libs/stream-utils": "^1.2.0",
- "meow": "^13.0.0"
+ "@simple-libs/stream-utils": "^2.0.0",
+ "argue-cli": "^3.1.0"
},
"bin": {
"conventional-commits-parser": "dist/cli/index.js"
},
"engines": {
- "node": ">=18"
+ "node": ">=22"
}
},
"node_modules/convert-source-map": {
@@ -9459,9 +10263,9 @@
}
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
- "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
"dev": true,
"funding": [
{
@@ -9616,21 +10420,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "license": "MIT",
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -9656,6 +10445,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -9679,6 +10477,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -9807,19 +10606,6 @@
"tslib": "^2.0.3"
}
},
- "node_modules/dot-prop": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
- "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-obj": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dotenv": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
@@ -9861,6 +10647,22 @@
"url": "https://dotenvx.com"
}
},
+ "node_modules/drizzle-kit": {
+ "version": "0.31.10",
+ "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz",
+ "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@drizzle-team/brocli": "^0.10.2",
+ "@esbuild-kit/esm-loader": "^2.5.5",
+ "esbuild": "^0.25.4",
+ "tsx": "^4.21.0"
+ },
+ "bin": {
+ "drizzle-kit": "bin.cjs"
+ }
+ },
"node_modules/drizzle-orm": {
"version": "0.45.2",
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz",
@@ -10042,9 +10844,9 @@
}
},
"node_modules/electron": {
- "version": "43.0.0",
- "resolved": "https://registry.npmjs.org/electron/-/electron-43.0.0.tgz",
- "integrity": "sha512-PV60GsWU6qufhuOhw3n+Yix3WPDcqDtBqE8orbEQGQGHEkgp9o/JCPgb7L4vIL0r1HnfPdqSRtboOTqbDkcFDQ==",
+ "version": "43.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-43.2.0.tgz",
+ "integrity": "sha512-80zvrgG7ZRXD+tD0IyLvrnN9n+veSxadMRsMaC9wKKP3iUbtC7rGM8+dVuCmOb0Rrwwv8ESW4awnUZh9Hbp1fA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10203,15 +11005,6 @@
"node": ">= 0.8"
}
},
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
"node_modules/enhanced-resolve": {
"version": "5.24.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz",
@@ -10249,19 +11042,6 @@
"node": ">=6"
}
},
- "node_modules/environment": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
- "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
@@ -10332,16 +11112,59 @@
}
},
"node_modules/es-toolkit": {
- "version": "1.47.0",
- "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz",
- "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==",
+ "version": "1.50.0",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz",
+ "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==",
"dev": true,
"license": "MIT",
"workspaces": [
"docs",
- "benchmarks"
+ "benchmarks",
+ "tests/types"
]
},
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -10372,9 +11195,9 @@
}
},
"node_modules/eslint": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz",
- "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==",
+ "version": "10.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz",
+ "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -10384,7 +11207,7 @@
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.5",
- "@eslint/config-helpers": "^0.6.0",
+ "@eslint/config-helpers": "^0.7.0",
"@eslint/core": "^1.2.1",
"@eslint/plugin-kit": "^0.7.2",
"@humanfs/node": "^0.16.6",
@@ -10408,7 +11231,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "minimatch": "^10.2.4",
+ "minimatch": "^10.2.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -10623,31 +11446,6 @@
"node": ">= 0.6"
}
},
- "node_modules/eventemitter3": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
- "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/events-universal": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
- "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
- "license": "Apache-2.0",
- "dependencies": {
- "bare-events": "^2.7.0"
- }
- },
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "license": "(MIT OR WTFPL)",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/expect-type": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
@@ -10740,12 +11538,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fast-fifo": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
- "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
- "license": "MIT"
- },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -10829,12 +11621,6 @@
"node": ">=16.0.0"
}
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "license": "MIT"
- },
"node_modules/filelist": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
@@ -10972,16 +11758,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -11027,12 +11813,12 @@
}
},
"node_modules/framer-motion": {
- "version": "12.42.2",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz",
- "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==",
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
+ "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
"license": "MIT",
"dependencies": {
- "motion-dom": "^12.42.2",
+ "motion-dom": "^12.43.0",
"motion-utils": "^12.39.0",
"tslib": "^2.4.0"
},
@@ -11101,6 +11887,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -11180,29 +11975,19 @@
"node": ">= 0.4"
}
},
- "node_modules/git-raw-commits": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz",
- "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==",
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@conventional-changelog/git-client": "^2.6.0",
- "meow": "^13.0.0"
+ "resolve-pkg-maps": "^1.0.0"
},
- "bin": {
- "git-raw-commits": "src/cli.js"
- },
- "engines": {
- "node": ">=18"
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "license": "MIT"
- },
"node_modules/glob": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz",
@@ -11257,9 +12042,9 @@
}
},
"node_modules/globals": {
- "version": "17.6.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz",
- "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==",
+ "version": "17.9.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz",
+ "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -11346,9 +12131,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -11519,13 +12304,13 @@
"license": "MIT"
},
"node_modules/html-parse-stringify": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
- "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz",
+ "integrity": "sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "void-elements": "3.1.0"
+ "funding": {
+ "url": "https://locize.com"
}
},
"node_modules/html-url-attributes": {
@@ -11849,16 +12634,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-obj": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
- "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -11885,6 +12660,12 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -12008,9 +12789,9 @@
}
},
"node_modules/jose": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
- "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "version": "6.2.7",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.7.tgz",
+ "integrity": "sha512-hq1OB1bALKfydZNoViyg6hPVGV4i93ny9Op+n4zP5RSf7SCZEXa/TsG2O3IEr7+WlHRTPnpqDmHfMH6qXAD60w==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -12031,9 +12812,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz",
- "integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.2.tgz",
+ "integrity": "sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==",
"funding": [
{
"type": "github",
@@ -12567,14 +13348,13 @@
"license": "MIT"
},
"node_modules/lint-staged": {
- "version": "17.0.8",
- "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.8.tgz",
- "integrity": "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==",
+ "version": "17.3.0",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz",
+ "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "listr2": "^10.2.1",
- "picomatch": "^4.0.4",
+ "picomatch": "^4.0.5",
"string-argv": "^0.3.2",
"tinyexec": "^1.2.4"
},
@@ -12608,103 +13388,6 @@
"url": "https://github.com/sponsors/eemeli"
}
},
- "node_modules/listr2": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz",
- "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cli-truncate": "^5.2.0",
- "eventemitter3": "^5.0.4",
- "log-update": "^6.1.0",
- "rfdc": "^1.4.1",
- "wrap-ansi": "^10.0.0"
- },
- "engines": {
- "node": ">=22.13.0"
- }
- },
- "node_modules/listr2/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/listr2/node_modules/cli-truncate": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
- "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "slice-ansi": "^8.0.0",
- "string-width": "^8.2.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/listr2/node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/listr2/node_modules/slice-ansi": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
- "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.3",
- "is-fullwidth-code-point": "^5.1.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/listr2/node_modules/string-width": {
- "version": "8.2.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
- "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -12770,114 +13453,11 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
- "node_modules/log-update": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
- "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-escapes": "^7.0.0",
- "cli-cursor": "^5.0.0",
- "slice-ansi": "^7.1.0",
- "strip-ansi": "^7.1.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/log-update/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/log-update/node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/log-update/node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/log-update/node_modules/slice-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
- "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "is-fullwidth-code-point": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/log-update/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/log-update/node_modules/wrap-ansi": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
- "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "string-width": "^7.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
},
"node_modules/longest-streak": {
"version": "3.1.0",
@@ -12938,10 +13518,25 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
"node_modules/lucide-react": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.20.0.tgz",
- "integrity": "sha512-jhXLeC/7m0/tjL1nzMdKk6x256zWA6AtbhTVreHOiKPoeX2d6MK4FbyIQPpVq0E6iPWBisyy1TW+pEge/uMEuQ==",
+ "version": "1.28.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz",
+ "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==",
"dev": true,
"license": "ISC",
"peerDependencies": {
@@ -13350,19 +13945,6 @@
"node": ">= 0.8"
}
},
- "node_modules/meow": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
- "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@@ -14022,31 +14604,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/mimic-function": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
- "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -14077,6 +14634,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -14105,19 +14663,13 @@
"node": ">= 18"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "license": "MIT"
- },
"node_modules/motion": {
- "version": "12.42.2",
- "resolved": "https://registry.npmjs.org/motion/-/motion-12.42.2.tgz",
- "integrity": "sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==",
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.43.0.tgz",
+ "integrity": "sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ==",
"license": "MIT",
"dependencies": {
- "framer-motion": "^12.42.2",
+ "framer-motion": "^12.43.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -14138,9 +14690,9 @@
}
},
"node_modules/motion-dom": {
- "version": "12.42.2",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz",
- "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==",
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
+ "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.39.0"
@@ -14230,6 +14782,40 @@
"node": ">= 0.6"
}
},
+ "node_modules/mysql2": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.23.2.tgz",
+ "integrity": "sha512-fxh3HpQ8vJtu/Mmnd4Xsur19jGjHGzRLMxptiDtOkbX7EVBgnafGSGDx1WGGVmJLClVh2LeeBMMo24IFv8wCyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.2",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.5.1"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/nan": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz",
@@ -14255,12 +14841,6 @@
"node": "^22 || ^24 || >=26"
}
},
- "node_modules/napi-build-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
- "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
- "license": "MIT"
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -14478,22 +15058,6 @@
"wrappy": "1"
}
},
- "node_modules/onetime": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
- "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-function": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -14761,6 +15325,95 @@
"url": "https://github.com/sponsors/jet2jet"
}
},
+ "node_modules/pg": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz",
+ "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.14.0",
+ "pg-pool": "^3.14.0",
+ "pg-protocol": "^1.15.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.4.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
+ "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
+ "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
+ "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz",
+ "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -14769,9 +15422,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -14884,43 +15537,43 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/prebuild-install": {
- "name": "@mmomtchev/prebuild-install",
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@mmomtchev/prebuild-install/-/prebuild-install-1.0.2.tgz",
- "integrity": "sha512-0Vje0eg5XQa8Ta1jtQiWqnT1kS/P7OAAvFJ4VZG5EXT2gJmpmPA5SrsDrh++BqNjIxYy8O6aF4wZjJc/k4JQ6w==",
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.3",
- "expand-template": "^2.0.3",
- "github-from-package": "^0.0.0",
- "minimist": "^1.2.8",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^1.0.2",
- "node-abi": "^3.63.0",
- "pump": "^3.0.0",
- "rc": "^1.2.8",
- "simple-get": "^4.0.1",
- "tar-fs": "^3.0.6",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
"engines": {
- "node": ">=18"
+ "node": ">=4"
}
},
- "node_modules/prebuild-install/node_modules/node-abi": {
- "version": "3.92.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
- "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
- "semver": "^7.3.5"
+ "xtend": "^4.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=0.10.0"
}
},
"node_modules/precond": {
@@ -14942,9 +15595,9 @@
}
},
"node_modules/prettier": {
- "version": "3.8.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz",
- "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==",
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"dev": true,
"license": "MIT",
"bin": {
@@ -15122,16 +15775,6 @@
"node": ">=10"
}
},
- "node_modules/pump": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
- "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -15341,67 +15984,67 @@
}
},
"node_modules/radix-ui": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.3.tgz",
- "integrity": "sha512-KmhSq0NfxIwN9q6ZpEaZ+J0hiVFQcGyrPYYhbxg34q9B8CIrQoccLJ3mJ9znLRslLoaogsP2ml8JKOVoKMXgvQ==",
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz",
+ "integrity": "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.6",
- "@radix-ui/react-accessible-icon": "1.1.12",
- "@radix-ui/react-accordion": "1.2.17",
- "@radix-ui/react-alert-dialog": "1.1.20",
- "@radix-ui/react-arrow": "1.1.12",
- "@radix-ui/react-aspect-ratio": "1.1.12",
- "@radix-ui/react-avatar": "1.2.3",
- "@radix-ui/react-checkbox": "1.3.8",
- "@radix-ui/react-collapsible": "1.1.17",
- "@radix-ui/react-collection": "1.1.12",
- "@radix-ui/react-compose-refs": "1.1.3",
- "@radix-ui/react-context": "1.2.0",
- "@radix-ui/react-context-menu": "2.3.4",
- "@radix-ui/react-dialog": "1.1.20",
- "@radix-ui/react-direction": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.16",
- "@radix-ui/react-dropdown-menu": "2.1.21",
- "@radix-ui/react-focus-guards": "1.1.4",
- "@radix-ui/react-focus-scope": "1.1.13",
- "@radix-ui/react-form": "0.1.13",
- "@radix-ui/react-hover-card": "1.1.20",
- "@radix-ui/react-label": "2.1.12",
- "@radix-ui/react-menu": "2.1.21",
- "@radix-ui/react-menubar": "1.1.21",
- "@radix-ui/react-navigation-menu": "1.2.19",
- "@radix-ui/react-one-time-password-field": "0.1.13",
- "@radix-ui/react-password-toggle-field": "0.1.8",
- "@radix-ui/react-popover": "1.1.20",
- "@radix-ui/react-popper": "1.3.4",
- "@radix-ui/react-portal": "1.1.14",
- "@radix-ui/react-presence": "1.1.8",
- "@radix-ui/react-primitive": "2.1.7",
- "@radix-ui/react-progress": "1.1.13",
- "@radix-ui/react-radio-group": "1.4.4",
- "@radix-ui/react-roving-focus": "1.1.16",
- "@radix-ui/react-scroll-area": "1.2.15",
- "@radix-ui/react-select": "2.3.4",
- "@radix-ui/react-separator": "1.1.12",
- "@radix-ui/react-slider": "1.4.4",
- "@radix-ui/react-slot": "1.3.0",
- "@radix-ui/react-switch": "1.3.4",
- "@radix-ui/react-tabs": "1.1.18",
- "@radix-ui/react-toast": "1.2.20",
- "@radix-ui/react-toggle": "1.1.15",
- "@radix-ui/react-toggle-group": "1.1.16",
- "@radix-ui/react-toolbar": "1.1.16",
- "@radix-ui/react-tooltip": "1.2.13",
- "@radix-ui/react-use-callback-ref": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.2.4",
- "@radix-ui/react-use-effect-event": "0.0.3",
- "@radix-ui/react-use-escape-keydown": "1.1.3",
- "@radix-ui/react-use-is-hydrated": "0.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.2",
- "@radix-ui/react-use-size": "1.1.2",
- "@radix-ui/react-visually-hidden": "1.2.8"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-accessible-icon": "1.1.15",
+ "@radix-ui/react-accordion": "1.2.20",
+ "@radix-ui/react-alert-dialog": "1.1.23",
+ "@radix-ui/react-arrow": "1.1.15",
+ "@radix-ui/react-aspect-ratio": "1.1.15",
+ "@radix-ui/react-avatar": "1.2.6",
+ "@radix-ui/react-checkbox": "1.3.11",
+ "@radix-ui/react-collapsible": "1.1.20",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-context-menu": "2.3.7",
+ "@radix-ui/react-dialog": "1.1.23",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-dropdown-menu": "2.1.24",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-form": "0.1.16",
+ "@radix-ui/react-hover-card": "1.1.23",
+ "@radix-ui/react-label": "2.1.15",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-menubar": "1.1.24",
+ "@radix-ui/react-navigation-menu": "1.2.22",
+ "@radix-ui/react-one-time-password-field": "0.1.16",
+ "@radix-ui/react-password-toggle-field": "0.1.11",
+ "@radix-ui/react-popover": "1.1.23",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-progress": "1.1.16",
+ "@radix-ui/react-radio-group": "1.4.7",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-scroll-area": "1.2.18",
+ "@radix-ui/react-select": "2.3.7",
+ "@radix-ui/react-separator": "1.1.15",
+ "@radix-ui/react-slider": "1.4.7",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-switch": "1.3.7",
+ "@radix-ui/react-tabs": "1.1.21",
+ "@radix-ui/react-toast": "1.2.23",
+ "@radix-ui/react-toggle": "1.1.18",
+ "@radix-ui/react-toggle-group": "1.1.19",
+ "@radix-ui/react-toolbar": "1.1.19",
+ "@radix-ui/react-tooltip": "1.2.16",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-escape-keydown": "1.1.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
"peerDependencies": {
"@types/react": "*",
@@ -15442,40 +16085,10 @@
"node": ">= 0.10"
}
},
- "node_modules/rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
- "dependencies": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "bin": {
- "rc": "cli.js"
- }
- },
- "node_modules/rc/node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "license": "ISC"
- },
- "node_modules/rc/node_modules/strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/react": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
- "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -15497,16 +16110,16 @@
}
},
"node_modules/react-dom": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
- "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.7"
+ "react": "^19.2.8"
}
},
"node_modules/react-h5-audio-player": {
@@ -15545,14 +16158,14 @@
}
},
"node_modules/react-i18next": {
- "version": "17.0.10",
- "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.10.tgz",
- "integrity": "sha512-XneHftyYA774MJkkccSkZ5oKrUpCnXIPmxio3wemqrVzCRLWiGXOMbIzObrer03fNDEnm8g8R5yYls4HcE+esg==",
+ "version": "17.0.11",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.11.tgz",
+ "integrity": "sha512-cDtkXgxjuFTWUH6V+aQn1Ve5vDiUztCNPWW5GtSHDccsgRXO1nE6QFWCEmc1KAutrb3OUv87wFShJL5RhUwPXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
- "html-parse-stringify": "^3.0.1",
+ "html-parse-stringify": "^4.0.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
@@ -15955,21 +16568,14 @@
"node": ">=8"
}
},
- "node_modules/restore-cursor": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
- "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "onetime": "^7.0.0",
- "signal-exit": "^4.1.0"
- },
- "engines": {
- "node": ">=18"
- },
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/retry": {
@@ -15982,13 +16588,6 @@
"node": ">= 4"
}
},
- "node_modules/rfdc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
- "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/rolldown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
@@ -16309,9 +16908,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.8.4",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
- "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
+ "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -16413,51 +17012,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/simple-concat": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -16586,6 +17140,30 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sql-escaper": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz",
+ "integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=2.0.0",
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
+ }
+ },
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
@@ -16651,17 +17229,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/streamx": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz",
- "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==",
- "license": "MIT",
- "dependencies": {
- "events-universal": "^1.0.0",
- "fast-fifo": "^1.3.2",
- "text-decoder": "^1.1.0"
- }
- },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -16882,32 +17449,6 @@
"node": ">=18"
}
},
- "node_modules/tar-fs": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
- "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0",
- "tar-stream": "^3.1.5"
- },
- "optionalDependencies": {
- "bare-fs": "^4.0.1",
- "bare-path": "^3.0.0"
- }
- },
- "node_modules/tar-stream": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
- "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
- "license": "MIT",
- "dependencies": {
- "b4a": "^1.6.4",
- "bare-fs": "^4.5.5",
- "fast-fifo": "^1.2.0",
- "streamx": "^2.15.0"
- }
- },
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
@@ -16918,15 +17459,6 @@
"node": ">=18"
}
},
- "node_modules/teex": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
- "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
- "license": "MIT",
- "dependencies": {
- "streamx": "^2.12.5"
- }
- },
"node_modules/temp-file": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
@@ -16938,15 +17470,6 @@
"fs-extra": "^10.0.0"
}
},
- "node_modules/text-decoder": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
- "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "b4a": "^1.6.4"
- }
- },
"node_modules/tiny-async-pool": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
@@ -17164,6 +17687,509 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tsx": {
+ "version": "4.23.1",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
+ "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
"node_modules/tsyringe": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz",
@@ -17182,18 +18208,6 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/tw-animate-css": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
@@ -17299,9 +18313,9 @@
}
},
"node_modules/undici": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.7.0.tgz",
- "integrity": "sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
+ "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
@@ -17837,16 +18851,6 @@
}
}
},
- "node_modules/void-elements": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
- "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
@@ -17975,54 +18979,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/wrap-ansi": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz",
- "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.3",
- "string-width": "^8.2.0",
- "strip-ansi": "^7.1.2"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/string-width": {
- "version": "8.2.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
- "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -18077,6 +19033,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index f66d52a0..e953f573 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "termix",
"private": true,
- "version": "2.6.0",
+ "version": "2.6.1",
"description": "Self-hosted SSH and remote desktop management.",
"author": "Karmaa",
"main": "electron/main.cjs",
@@ -16,10 +16,11 @@
"biome:fix": "biome check --write biome.json package.json",
"postinstall": "node scripts/patch-app-builder-lib.cjs && node scripts/patch-guacamole-lite.cjs && node scripts/patch-guacamole-common-js.cjs && node scripts/patch-better-sqlite3.cjs && node scripts/patch-nan.cjs && node scripts/patch-xterm-android-ime.cjs",
"prebuild": "node scripts/write-electron-build-info.cjs",
- "lint": "eslint .",
+ "lint": "node scripts/generate-dialect-schema.cjs --check && eslint .",
"lint:fix": "eslint --fix .",
"type-check": "tsc --noEmit",
"test": "vitest run",
+ "verify:dialect": "tsx scripts/verify-dialects.mjs",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
@@ -40,77 +41,82 @@
"build:linux-appimage": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --linux AppImage",
"build:linux-targz": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --linux tar.gz",
"build:mac": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac --universal",
- "build:mac-dev": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac dir --publish=never"
+ "build:mac-dev": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac dir --publish=never",
+ "schema:generate": "node scripts/generate-dialect-schema.cjs",
+ "schema:check": "node scripts/generate-dialect-schema.cjs --check",
+ "schema:migrations": "drizzle-kit generate --config=drizzle.config.sqlite.ts && drizzle-kit generate --config=drizzle.config.pg.ts && drizzle-kit generate --config=drizzle.config.mysql.ts"
},
"dependencies": {
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.2",
- "@tanstack/react-virtual": "^3.14.6",
+ "@tanstack/react-virtual": "^3.14.9",
"@types/ldapjs": "^3.0.6",
- "axios": "^1.18.1",
+ "axios": "^1.19.0",
"bcryptjs": "^3.0.3",
- "better-sqlite3": "^12.11.1",
+ "better-sqlite3": "^13.0.2",
"body-parser": "^2.3.0",
- "chalk": "^5.6.2",
+ "chalk": "^6.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"drizzle-orm": "^0.45.2",
"express": "^5.2.1",
"guacamole-lite": "^1.2.0",
- "jose": "^6.2.2",
- "js-yaml": "^5.2.1",
+ "jose": "^6.2.5",
+ "js-yaml": "^5.2.2",
"jsonwebtoken": "^9.0.3",
"jszip": "^3.10.1",
"ldapjs": "^3.0.7",
- "motion": "^12.42.2",
+ "motion": "^12.43.0",
"multer": "^2.2.0",
+ "mysql2": "^3.23.2",
"nanoid": "^6.0.0",
+ "pg": "^8.22.0",
"qrcode": "^1.5.4",
"serialport": "^13.0.0",
"socks": "^2.8.7",
"speakeasy": "^2.0.0",
"ssh2": "^1.17.0",
- "undici": "^8.7.0",
+ "undici": "^8.9.0",
"ws": "^8.21.1"
},
"devDependencies": {
- "@biomejs/biome": "2.5.4",
+ "@biomejs/biome": "2.5.6",
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/commands": "^6.10.4",
"@codemirror/search": "^6.7.1",
"@codemirror/theme-one-dark": "^6.1.3",
- "@codemirror/view": "^6.43.6",
- "@commitlint/cli": "^21.0.2",
- "@commitlint/config-conventional": "^21.0.2",
+ "@codemirror/view": "^6.43.7",
+ "@commitlint/cli": "^21.2.1",
+ "@commitlint/config-conventional": "^21.2.0",
"@deadendjs/swagger-jsdoc": "^8.1.2",
"@electron/notarize": "^3.1.1",
- "@electron/rebuild": "^4.0.4",
+ "@electron/rebuild": "^4.2.0",
"@eslint/js": "^10.0.1",
- "@fontsource-variable/jetbrains-mono": "^5.2.8",
- "@fontsource/fira-code": "^5.2.7",
- "@fontsource/jetbrains-mono": "^5.2.8",
- "@fontsource/source-code-pro": "^5.2.7",
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
+ "@fontsource/fira-code": "^5.3.0",
+ "@fontsource/jetbrains-mono": "^5.3.0",
+ "@fontsource/source-code-pro": "^5.3.0",
"@monaco-editor/react": "^4.7.0",
- "@radix-ui/react-accordion": "^1.2.17",
- "@radix-ui/react-alert-dialog": "^1.1.20",
- "@radix-ui/react-checkbox": "^1.3.8",
- "@radix-ui/react-dialog": "^1.1.20",
- "@radix-ui/react-dropdown-menu": "^2.1.21",
- "@radix-ui/react-label": "^2.1.12",
- "@radix-ui/react-popover": "^1.1.20",
- "@radix-ui/react-progress": "^1.1.13",
- "@radix-ui/react-scroll-area": "^1.2.15",
- "@radix-ui/react-select": "^2.3.4",
- "@radix-ui/react-separator": "^1.1.12",
- "@radix-ui/react-slider": "^1.4.4",
- "@radix-ui/react-slot": "^1.3.0",
- "@radix-ui/react-switch": "^1.3.4",
- "@radix-ui/react-tabs": "^1.1.18",
- "@radix-ui/react-tooltip": "^1.2.13",
+ "@radix-ui/react-accordion": "^1.2.20",
+ "@radix-ui/react-alert-dialog": "^1.1.23",
+ "@radix-ui/react-checkbox": "^1.3.11",
+ "@radix-ui/react-dialog": "^1.1.23",
+ "@radix-ui/react-dropdown-menu": "^2.1.24",
+ "@radix-ui/react-label": "^2.1.15",
+ "@radix-ui/react-popover": "^1.1.23",
+ "@radix-ui/react-progress": "^1.1.16",
+ "@radix-ui/react-scroll-area": "^1.2.18",
+ "@radix-ui/react-select": "^2.3.7",
+ "@radix-ui/react-separator": "^1.1.15",
+ "@radix-ui/react-slider": "^1.4.7",
+ "@radix-ui/react-slot": "^1.3.3",
+ "@radix-ui/react-switch": "^1.3.7",
+ "@radix-ui/react-tabs": "^1.1.21",
+ "@radix-ui/react-tooltip": "^1.2.16",
"@tailwindcss/vite": "^4.3.3",
"@testing-library/dom": "^10.4.1",
- "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/better-sqlite3": "^7.6.13",
@@ -120,52 +126,55 @@
"@types/guacamole-common-js": "^1.5.5",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.10",
- "@types/multer": "^2.1.0",
- "@types/node": "^26.0.0",
+ "@types/multer": "^2.2.0",
+ "@types/node": "^26.1.2",
+ "@types/pg": "^8.20.0",
"@types/qrcode": "^1.5.6",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.4",
"@types/speakeasy": "^2.0.10",
"@types/ssh2": "^1.15.5",
"@types/ws": "^8.18.1",
"@uiw/codemirror-extensions-langs": "^4.25.11",
"@uiw/codemirror-theme-github": "^4.25.11",
"@uiw/react-codemirror": "^4.25.11",
- "@vitejs/plugin-react": "^6.0.3",
+ "@vitejs/plugin-react": "^6.0.5",
"@vitest/coverage-v8": "^4.1.10",
"@vitest/ui": "^4.1.10",
"@xterm/addon-clipboard": "^0.2.0",
"@xterm/addon-fit": "^0.11.0",
+ "@xterm/addon-search": "^0.16.0",
"@xterm/addon-unicode11": "^0.9.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
- "concurrently": "^10.0.3",
+ "concurrently": "^10.0.4",
"cytoscape": "^3.34.0",
- "electron": "^43.0.0",
+ "drizzle-kit": "^0.31.10",
+ "electron": "^43.2.0",
"electron-builder": "^26.15.3",
- "eslint": "^10.5.0",
+ "eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"eslint-plugin-unused-imports": "^4.4.1",
- "globals": "^17.5.0",
+ "globals": "^17.8.0",
"guacamole-common-js": "^1.5.0",
"husky": "^9.1.7",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"jsdom": "^29.1.1",
- "lint-staged": "^17.0.8",
- "lucide-react": "^1.20.0",
- "prettier": "3.8.4",
- "radix-ui": "^1.6.3",
- "react": "^19.2.7",
+ "lint-staged": "^17.2.0",
+ "lucide-react": "^1.28.0",
+ "prettier": "3.9.6",
+ "radix-ui": "^1.6.7",
+ "react": "^19.2.8",
"react-cytoscapejs": "^2.0.0",
- "react-dom": "^19.2.7",
+ "react-dom": "^19.2.8",
"react-h5-audio-player": "^3.10.2",
"react-hook-form": "^7.79.0",
- "react-i18next": "^17.0.10",
+ "react-i18next": "^17.0.11",
"react-icons": "^5.6.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.4.1",
diff --git a/scripts/generate-dialect-schema.cjs b/scripts/generate-dialect-schema.cjs
new file mode 100644
index 00000000..06c5b0c4
--- /dev/null
+++ b/scripts/generate-dialect-schema.cjs
@@ -0,0 +1,228 @@
+/**
+ * Generates the Postgres and MySQL schema modules from the SQLite one.
+ *
+ * ## These files produce DDL. They are not used at runtime.
+ *
+ * drizzle-kit reads them to emit the migrations in drizzle/postgres and
+ * drizzle/mysql. Nothing imports them to run a query.
+ *
+ * That is not an oversight. The query builder needs two things from a table
+ * object — the identifiers to interpolate, and the encoders that turn JS values
+ * into driver values — and the sqlite definitions supply both correctly for
+ * every engine, which is why all 44 repositories import schema.ts directly:
+ *
+ * - text and integer encode as themselves everywhere
+ * - integer({ mode: "boolean" }) writes 1/0, which Postgres and MySQL both
+ * accept for a boolean column, and reads back through `Number(v) === 1`,
+ * which is true for JS `true` as well as for 1
+ * - real is a plain number on all three
+ *
+ * What genuinely differs between the dialects is DDL — column types, key
+ * lengths, autoincrement syntax — and DDL is exactly what these files exist to
+ * generate. See scripts/verify-dialects.mjs, which asserts the round-trips
+ * above against real servers rather than trusting this comment.
+ *
+ * The schema is declared once, in sqlite-core, and the other two dialects are
+ * derived. Hand-maintaining three copies of 52 tables would mean a renamed
+ * table has to land in three places consistently or a foreign key silently
+ * points at the wrong one — and the schema is regular enough that the mapping
+ * is mechanical.
+ *
+ * What varies between dialects is small and closed:
+ * - booleans are integers on sqlite, native elsewhere
+ * - autoincrement keys are `integer primary key autoincrement`, `serial`,
+ * and `int auto_increment`
+ * - MySQL cannot index unbounded TEXT, so any column that is a primary key,
+ * is unique, or participates in a foreign key must be varchar
+ * - MySQL rejects a bare DEFAULT CURRENT_TIMESTAMP on a text column, so it is
+ * written as a parenthesised expression default
+ *
+ * Usage: node scripts/generate-dialect-schema.cjs [--check]
+ * --check verifies the committed files match what would be generated,
+ * for CI to catch a schema edit that forgot to regenerate.
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+const ROOT = path.join(__dirname, "..");
+const SOURCE = path.join(ROOT, "src/backend/database/db/schema.ts");
+const TARGETS = {
+ postgres: path.join(ROOT, "src/backend/database/db/schema.pg.ts"),
+ mysql: path.join(ROOT, "src/backend/database/db/schema.mysql.ts"),
+};
+
+const KEY_LENGTH = 255;
+
+/**
+ * Columns that must be varchar rather than text on MySQL. A column qualifies if
+ * it is a primary key, is unique, or is either end of a foreign key.
+ */
+function collectKeyColumns(source) {
+ const keyed = new Set();
+
+ // `name: text("col")....primaryKey()` / `.unique()` / `.references(...)`
+ const declaration =
+ /(\w+):\s*text\("([a-z0-9_]+)"\)((?:\s*\.\w+\([^)]*\))*)/g;
+ let match;
+ while ((match = declaration.exec(source)) !== null) {
+ const [, prop, column, modifiers] = match;
+ if (/\.(primaryKey|unique|references)\(/.test(modifiers)) {
+ keyed.add(column);
+ }
+ void prop;
+ }
+
+ // Multi-line form: the modifiers land on following lines.
+ const multiline =
+ /(\w+):\s*text\("([a-z0-9_]+)"\)\s*\n(\s*\.\w+\([\s\S]*?\),)/g;
+ while ((match = multiline.exec(source)) !== null) {
+ if (/\.(primaryKey|unique|references)\(/.test(match[3])) {
+ keyed.add(match[2]);
+ }
+ }
+
+ // A referenced column implies the referencing side too; both must match.
+ const reference = /\.references\(\(\)\s*=>\s*\w+\.(\w+)/g;
+ while ((match = reference.exec(source)) !== null) {
+ keyed.add(camelToSnake(match[1]));
+ }
+
+ // Table-level indexes: `(table) => [uniqueIndex("x").on(table.a, table.b)]`.
+ // These were invisible here at first, and MySQL rejected the migration with
+ // "BLOB/TEXT column used in key specification without a key length" — but
+ // only on MySQL 8; MariaDB took it.
+ const tableIndex = /uniqueIndex\("[a-z0-9_]+"\)\.on\(([^)]*)\)/g;
+ while ((match = tableIndex.exec(source)) !== null) {
+ for (const column of match[1].split(",")) {
+ const name = column.trim().replace(/^\w+\./, "");
+ if (name) keyed.add(camelToSnake(name));
+ }
+ }
+
+ return keyed;
+}
+
+function camelToSnake(value) {
+ return value.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
+}
+
+function transform(source, dialect) {
+ const keyed = collectKeyColumns(source);
+ const isPg = dialect === "postgres";
+ let out = source;
+
+ // Autoincrement primary keys, before the plain integer rule below.
+ out = out.replace(
+ /integer\("([a-z0-9_]+)"\)\.primaryKey\(\{\s*autoIncrement:\s*true\s*\}\)/g,
+ (_, col) =>
+ isPg
+ ? `serial("${col}").primaryKey()`
+ : `int("${col}").autoincrement().primaryKey()`,
+ );
+
+ // Integer-backed booleans become native ones. Prettier wraps the longer
+ // declarations across lines, so this has to span newlines too.
+ out = out.replace(
+ /integer\(\s*"([a-z0-9_]+)",\s*\{\s*mode:\s*"boolean",?\s*\},?\s*\)/g,
+ (_, col) => `boolean("${col}")`,
+ );
+
+ // Remaining integers.
+ if (!isPg) {
+ out = out.replace(
+ /\binteger\("([a-z0-9_]+)"\)/g,
+ (_, col) => `int("${col}")`,
+ );
+
+ // Timestamps are stored as text (see sql-timestamp.ts). MySQL only accepts
+ // DEFAULT CURRENT_TIMESTAMP on a DATETIME or TIMESTAMP column — on a TEXT
+ // one it is ER_INVALID_DEFAULT, "Invalid default value". Since 8.0.13 an
+ // expression default works on any type, and an expression is written
+ // parenthesised. MariaDB accepts the bare form, which is why this only
+ // surfaces against real MySQL.
+ out = out.replace(/sql`CURRENT_TIMESTAMP`/g, "sql`(CURRENT_TIMESTAMP)`");
+ }
+
+ // Floating point.
+ out = out.replace(/\breal\("([a-z0-9_]+)"\)/g, (_, col) =>
+ isPg ? `doublePrecision("${col}")` : `double("${col}")`,
+ );
+
+ // Key-bearing strings must be indexable.
+ out = out.replace(/\btext\("([a-z0-9_]+)"\)/g, (whole, col) =>
+ keyed.has(col) ? `varchar("${col}", { length: ${KEY_LENGTH} })` : whole,
+ );
+
+ // text("x", { length: n }) is sqlite-only sugar; drop the length.
+ out = out.replace(
+ /\btext\("([a-z0-9_]+)",\s*\{\s*length:\s*\d+\s*\}\)/g,
+ (_, col) => `text("${col}")`,
+ );
+
+ out = out.replace(/\bsqliteTable\(/g, isPg ? "pgTable(" : "mysqlTable(");
+
+ const imports = isPg
+ ? `import {\n pgTable,\n text,\n varchar,\n integer,\n serial,\n boolean,\n doublePrecision,\n uniqueIndex,\n} from "drizzle-orm/pg-core";`
+ : `import {\n mysqlTable,\n text,\n varchar,\n int,\n boolean,\n double,\n uniqueIndex,\n} from "drizzle-orm/mysql-core";`;
+
+ out = out.replace(
+ /import\s*\{[^}]*\}\s*from\s*"drizzle-orm\/sqlite-core";/,
+ imports,
+ );
+
+ return `${header(dialect)}\n${out}`;
+}
+
+function header(dialect) {
+ return `// GENERATED FILE — do not edit.
+//
+// Produced from schema.ts by scripts/generate-dialect-schema.cjs.
+// Edit the sqlite schema and re-run \`node scripts/generate-dialect-schema.cjs\`.
+// Target dialect: ${dialect}.
+//
+// DDL source for drizzle-kit. NOT imported to run queries — repositories use
+// schema.ts on every dialect. See the generator header for why that is correct.
+`;
+}
+
+function main() {
+ const check = process.argv.includes("--check");
+ const source = fs.readFileSync(SOURCE, "utf8");
+ let drift = false;
+
+ for (const [dialect, target] of Object.entries(TARGETS)) {
+ const generated = transform(source, dialect);
+
+ if (check) {
+ const current = fs.existsSync(target)
+ ? fs.readFileSync(target, "utf8")
+ : "";
+ if (current !== generated) {
+ console.error(
+ `[generate-dialect-schema] ${path.relative(ROOT, target)} is out of date`,
+ );
+ drift = true;
+ }
+ continue;
+ }
+
+ fs.writeFileSync(target, generated);
+ console.log(
+ `[generate-dialect-schema] wrote ${path.relative(ROOT, target)}`,
+ );
+ }
+
+ if (drift) {
+ console.error(
+ "[generate-dialect-schema] run `node scripts/generate-dialect-schema.cjs` and commit the result",
+ );
+ process.exit(1);
+ }
+}
+
+module.exports = { transform, collectKeyColumns };
+
+if (require.main === module) {
+ main();
+}
diff --git a/scripts/generate-dialect-schema.test.ts b/scripts/generate-dialect-schema.test.ts
new file mode 100644
index 00000000..dad931f3
--- /dev/null
+++ b/scripts/generate-dialect-schema.test.ts
@@ -0,0 +1,140 @@
+import { describe, expect, it } from "vitest";
+import { createRequire } from "node:module";
+
+const require = createRequire(import.meta.url);
+const { transform, collectKeyColumns } =
+ require("./generate-dialect-schema.cjs") as {
+ transform: (source: string, dialect: "postgres" | "mysql") => string;
+ collectKeyColumns: (source: string) => Set;
+ };
+
+const SOURCE = `import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
+import { sql } from "drizzle-orm";
+
+export const users = sqliteTable("users", {
+ id: text("id").primaryKey(),
+ username: text("username").notNull(),
+ isAdmin: integer("is_admin", { mode: "boolean" }).notNull().default(false),
+ wrapped: integer("wrapped", {
+ mode: "boolean",
+ })
+ .notNull()
+ .default(true),
+ score: real("score"),
+ ssoProviderId: integer("sso_provider_id"),
+});
+
+export const folders = sqliteTable("folders", {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: text("name").notNull(),
+ syncId: text("sync_id").unique(),
+ cert: text("cert", { length: 8192 }),
+});
+`;
+
+describe("collectKeyColumns", () => {
+ it("finds columns that must be indexable", () => {
+ const keyed = collectKeyColumns(SOURCE);
+
+ // primary key, unique, and both ends of the foreign key
+ expect(keyed.has("id")).toBe(true);
+ expect(keyed.has("sync_id")).toBe(true);
+ expect(keyed.has("user_id")).toBe(true);
+ });
+
+ it("leaves ordinary strings alone", () => {
+ const keyed = collectKeyColumns(SOURCE);
+
+ expect(keyed.has("username")).toBe(false);
+ expect(keyed.has("name")).toBe(false);
+ expect(keyed.has("cert")).toBe(false);
+ });
+});
+
+describe("postgres output", () => {
+ const out = transform(SOURCE, "postgres");
+
+ it("is marked generated", () => {
+ expect(out.startsWith("// GENERATED FILE")).toBe(true);
+ });
+
+ it("uses pg-core", () => {
+ expect(out).toContain('from "drizzle-orm/pg-core"');
+ expect(out).not.toContain("sqlite-core");
+ expect(out).toContain("pgTable(");
+ expect(out).not.toContain("sqliteTable(");
+ });
+
+ it("maps autoincrement keys to serial", () => {
+ expect(out).toContain('serial("id").primaryKey()');
+ expect(out).not.toContain("autoIncrement");
+ });
+
+ it("maps integer-backed booleans, including the wrapped form", () => {
+ expect(out).toContain('boolean("is_admin")');
+ // Prettier splits longer declarations across lines; both must convert.
+ expect(out).toContain('boolean("wrapped")');
+ expect(out).not.toMatch(/mode:\s*"boolean"/);
+ });
+
+ it("keeps plain integers and maps real", () => {
+ expect(out).toContain('integer("sso_provider_id")');
+ expect(out).toContain('doublePrecision("score")');
+ });
+
+ it("makes key columns varchar and leaves the rest text", () => {
+ expect(out).toContain('varchar("id", { length: 255 })');
+ expect(out).toContain('varchar("user_id", { length: 255 })');
+ expect(out).toContain('varchar("sync_id", { length: 255 })');
+ expect(out).toContain('text("username")');
+ expect(out).toContain('text("name")');
+ });
+
+ it("drops the sqlite-only text length", () => {
+ expect(out).toContain('text("cert")');
+ expect(out).not.toContain("length: 8192");
+ });
+});
+
+describe("mysql output", () => {
+ const out = transform(SOURCE, "mysql");
+
+ it("uses mysql-core", () => {
+ expect(out).toContain('from "drizzle-orm/mysql-core"');
+ expect(out).toContain("mysqlTable(");
+ });
+
+ it("maps autoincrement keys to int auto_increment", () => {
+ expect(out).toContain('int("id").autoincrement().primaryKey()');
+ });
+
+ it("renames integer to int", () => {
+ expect(out).toContain('int("sso_provider_id")');
+ expect(out).not.toMatch(/\binteger\(/);
+ });
+
+ it("maps real to double", () => {
+ expect(out).toContain('double("score")');
+ });
+
+ it("makes key columns varchar — MySQL cannot index unbounded TEXT", () => {
+ expect(out).toContain('varchar("user_id", { length: 255 })');
+ expect(out).toContain('text("name")');
+ });
+});
+
+describe("determinism", () => {
+ it("produces identical output for identical input", () => {
+ expect(transform(SOURCE, "postgres")).toBe(transform(SOURCE, "postgres"));
+ expect(transform(SOURCE, "mysql")).toBe(transform(SOURCE, "mysql"));
+ });
+
+ it("keeps foreign key behaviour verbatim", () => {
+ for (const dialect of ["postgres", "mysql"] as const) {
+ expect(transform(SOURCE, dialect)).toContain('onDelete: "cascade"');
+ }
+ });
+});
diff --git a/scripts/patch-guacamole-lite.cjs b/scripts/patch-guacamole-lite.cjs
index b9a76efe..c3d37bce 100644
--- a/scripts/patch-guacamole-lite.cjs
+++ b/scripts/patch-guacamole-lite.cjs
@@ -35,6 +35,20 @@ if (
process.exit(0);
}
+// Every patch below is required for correctness: protocol negotiation, the
+// guacd 1.6.0 name handshake, dynamic argument answering, UTF-8 tokens and
+// read-only joins. If an upstream release moves an anchor string, silently
+// skipping would ship a Termix that looks fine and then drops VNC/RDP sessions
+// at runtime, so a missing anchor has to stop the install instead.
+function missingAnchor(patch) {
+ console.error(
+ `[patch-guacamole-lite] ${patch} anchor not found in guacamole-lite. ` +
+ "The upstream file has changed and this patch no longer applies — " +
+ "update scripts/patch-guacamole-lite.cjs to match the new source.",
+ );
+ process.exit(1);
+}
+
let guacdClientContent = fs.readFileSync(guacdClientPath, "utf8");
let cryptContent = fs.readFileSync(cryptPath, "utf8");
let clientConnectionContent = fs.readFileSync(clientConnectionPath, "utf8");
@@ -157,18 +171,14 @@ if (!guacdClientContent.includes("} else if (/^1_\\d+_0$/.test(version)) {")) {
newVersionBlock,
);
} else {
- console.log(
- "[patch-guacamole-lite] Version check target not found, skipping",
- );
- process.exit(0);
+ missingAnchor("Version check");
}
patched = true;
}
if (!guacdClientContent.includes(newTimezone)) {
if (!guacdClientContent.includes(oldTimezone)) {
- console.log("[patch-guacamole-lite] Timezone target not found, skipping");
- process.exit(0);
+ missingAnchor("Timezone");
}
guacdClientContent = guacdClientContent.replace(oldTimezone, newTimezone);
patched = true;
@@ -180,20 +190,14 @@ if (!guacdClientContent.includes(newConnect)) {
} else if (guacdClientContent.includes(oldConnect)) {
guacdClientContent = guacdClientContent.replace(oldConnect, newConnect);
} else {
- console.log(
- "[patch-guacamole-lite] Connect target not found, skipping name patch",
- );
- process.exit(0);
+ missingAnchor("Connect");
}
patched = true;
}
if (!guacdClientContent.includes("this.nextArgumentStreamIndex = 0;")) {
if (!guacdClientContent.includes(oldSendBuffer)) {
- console.log(
- "[patch-guacamole-lite] Argument stream index target not found, skipping",
- );
- process.exit(0);
+ missingAnchor("Argument stream index");
}
guacdClientContent = guacdClientContent.replace(oldSendBuffer, newSendBuffer);
patched = true;
@@ -201,10 +205,7 @@ if (!guacdClientContent.includes("this.nextArgumentStreamIndex = 0;")) {
if (!guacdClientContent.includes("sendRequiredArguments(params) {")) {
if (!guacdClientContent.includes(oldSendInstructionBlock)) {
- console.log(
- "[patch-guacamole-lite] Required argument helper target not found, skipping",
- );
- process.exit(0);
+ missingAnchor("Required argument helper");
}
guacdClientContent = guacdClientContent.replace(
oldSendInstructionBlock,
@@ -217,10 +218,7 @@ if (
!guacdClientContent.includes("opcode === 'required' || opcode === 'require'")
) {
if (!guacdClientContent.includes(oldReadyHandler)) {
- console.log(
- "[patch-guacamole-lite] Required opcode target not found, skipping",
- );
- process.exit(0);
+ missingAnchor("Required opcode");
}
guacdClientContent = guacdClientContent.replace(
oldReadyHandler,
@@ -273,10 +271,7 @@ if (!cryptContent.includes(newDecryptBlock)) {
newDecryptBlock,
);
} else {
- console.log(
- "[patch-guacamole-lite] UTF-8 token decrypt target not found, skipping",
- );
- process.exit(0);
+ missingAnchor("UTF-8 token decrypt");
}
patched = true;
}
@@ -329,10 +324,7 @@ const newSendMessageToGuacd =
if (!clientConnectionContent.includes("isReadOnlyJoin()")) {
if (!clientConnectionContent.includes(oldSendMessageToGuacd)) {
- console.log(
- "[patch-guacamole-lite] sendMessageToGuacd target not found, skipping read-only patch",
- );
- process.exit(0);
+ missingAnchor("sendMessageToGuacd");
}
clientConnectionContent = clientConnectionContent.replace(
oldSendMessageToGuacd,
@@ -357,10 +349,7 @@ const newPreserveJoin =
if (!clientConnectionContent.includes("compiledSettings.readOnly")) {
if (!clientConnectionContent.includes(oldPreserveJoin)) {
- console.log(
- "[patch-guacamole-lite] join-preserve target not found, skipping readOnly propagation patch",
- );
- process.exit(0);
+ missingAnchor("join-preserve");
}
clientConnectionContent = clientConnectionContent.replace(
oldPreserveJoin,
diff --git a/scripts/verify-dialects.mjs b/scripts/verify-dialects.mjs
new file mode 100644
index 00000000..308a530b
--- /dev/null
+++ b/scripts/verify-dialects.mjs
@@ -0,0 +1,140 @@
+/**
+ * Runs the repository layer against a real Postgres or MySQL server.
+ *
+ * The unit tests only ever see SQLite, so the parts of this codebase that
+ * differ per engine — the RETURNING replacements, the read-then-write
+ * transactions, the value encoders — have no coverage there at all. This is
+ * what covers them, and it needs a live server, which is why it is a script
+ * rather than a test.
+ *
+ * Usage:
+ * npm run verify:dialect -- postgres://user:pass@host:5432/db
+ * npm run verify:dialect -- mysql://user:pass@host:3306/db
+ *
+ * Applies the migrations first, through the same runRemoteMigrations() the
+ * application uses at startup — so a broken migration fails here rather than in
+ * production. Writes real rows: point it at a scratch database.
+ */
+
+import { randomUUID } from "crypto";
+
+const url = process.argv[2];
+if (!url) {
+ console.error("usage: node scripts/verify-dialects.mjs ");
+ process.exit(2);
+}
+
+const scheme = url.split("://", 1)[0].toLowerCase();
+const dialect = scheme.startsWith("postgres")
+ ? "postgres"
+ : scheme === "mysql" || scheme === "mariadb"
+ ? "mysql"
+ : null;
+
+if (!dialect) {
+ console.error(`unsupported URL scheme "${scheme}://"`);
+ process.exit(2);
+}
+
+const { drizzle } = await import(
+ dialect === "postgres" ? "drizzle-orm/node-postgres" : "drizzle-orm/mysql2"
+);
+
+// No schema option on purpose — see connect.ts.
+const db = drizzle(url);
+const context = { dialect, drizzle: db };
+
+const { runRemoteMigrations } =
+ await import("../src/backend/database/db/migrate.js");
+await runRemoteMigrations(dialect, db);
+
+const { UserRepository } =
+ await import("../src/backend/database/repositories/user-repository.js");
+const { HostRepository } =
+ await import("../src/backend/database/repositories/host-repository.js");
+const { SettingsRepository } =
+ await import("../src/backend/database/repositories/settings-repository.js");
+
+let failures = 0;
+const check = (label, got, want) => {
+ const ok = JSON.stringify(got) === JSON.stringify(want);
+ if (!ok) failures++;
+ console.log(
+ ` ${ok ? "ok " : "FAIL"} ${label}` +
+ (ok
+ ? ""
+ : `\n got ${JSON.stringify(got)}, want ${JSON.stringify(want)}`),
+ );
+};
+
+console.log(`\nverifying ${dialect} at ${url.replace(/:[^:@]*@/, ":***@")}\n`);
+
+const users = new UserRepository(context);
+const userId = `verify-${randomUUID()}`;
+
+// insertReturning: on MySQL this is an insert plus a read inside a transaction.
+const created = await users.create({
+ id: userId,
+ username: "before",
+ passwordHash: "x",
+ isAdmin: true,
+});
+check("insert returns the stored row", created?.username, "before");
+
+// The one non-identity value encoder in the schema. Booleans are integers in
+// the sqlite definitions the repositories import, so this asserts that 1/0
+// survives a round trip through a native boolean column.
+check("boolean true survives the round trip", created?.isAdmin, true);
+
+// updateReturning must report the state AFTER the write. Reading first would
+// return the value the update replaced — silently, with no error.
+const updated = await users.update(userId, { username: "after" });
+check("update returns the new value", updated?.username, "after");
+
+const hosts = new HostRepository(context);
+const host = await hosts.create({
+ userId,
+ name: "verify",
+ ip: "127.0.0.1",
+ port: 22,
+ username: "root",
+ authType: "password",
+ enableTerminal: true,
+});
+check(
+ "autoincrement id came back",
+ typeof host?.id === "number" && host.id > 0,
+ true,
+);
+check(
+ "database-assigned createdAt came back",
+ typeof host?.createdAt === "string" && host.createdAt.length > 0,
+ true,
+);
+
+// deleteReturning must report the state BEFORE the write. Reading afterwards
+// would find nothing at all.
+const settings = new SettingsRepository(context);
+const prefix = `verify-${randomUUID()}`;
+await settings.set(`${prefix}-a`, "1");
+await settings.set(`${prefix}-b`, "2");
+check(
+ "delete reports the rows it removed",
+ await settings.deleteLike(`${prefix}-%`),
+ 2,
+);
+check(
+ "and they are actually gone",
+ (await settings.listAll()).filter((row) => row.key.startsWith(prefix)).length,
+ 0,
+);
+
+await hosts.deleteForUser(userId, host.id);
+check("host really deleted", await hosts.findById(host.id), null);
+
+console.log(
+ failures === 0
+ ? `\n${dialect}: all checks passed\n`
+ : `\n${dialect}: ${failures} FAILED\n`,
+);
+process.exit(failures === 0 ? 0 : 1);
diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts
index 28e5d051..8950ae41 100644
--- a/src/backend/database/database.ts
+++ b/src/backend/database/database.ts
@@ -713,7 +713,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
- const filename = `termix-export-${user[0].username}-${timestamp}.sqlite`;
+ const filename = `termix-export-${user.username}-${timestamp}.sqlite`;
const tempPath = path.join(tempDir, filename);
apiLogger.info("Creating export database", {
@@ -882,7 +882,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
);
`);
- const userRecord = user[0];
+ const userRecord = user;
const insertUser = exportDb.prepare(`
INSERT INTO users (id, username, password_hash, is_admin, is_oidc, oidc_identifier, client_id, client_secret, issuer_url, authorization_url, token_url, identifier_path, name_path, scopes, totp_secret, totp_enabled, totp_backup_codes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
diff --git a/src/backend/database/db/column-kit.ts b/src/backend/database/db/column-kit.ts
new file mode 100644
index 00000000..bcda1b8d
--- /dev/null
+++ b/src/backend/database/db/column-kit.ts
@@ -0,0 +1,71 @@
+import * as sqlite from "drizzle-orm/sqlite-core";
+import * as pg from "drizzle-orm/pg-core";
+import * as mysql from "drizzle-orm/mysql-core";
+
+/**
+ * Per-dialect column constructors, so a table can be declared once instead of
+ * three times.
+ *
+ * The existing schema only uses three column types (text, integer, real) plus
+ * an integer-backed boolean, which is what makes this tractable — the surface
+ * to abstract is small and closed. Anything a dialect cannot express the same
+ * way is spelled out here rather than at 52 call sites.
+ *
+ * Notable differences this papers over:
+ * - booleans are integers in SQLite, native in Postgres and tinyint in MySQL
+ * - autoincrement keys are `integer primary key autoincrement`, `serial`, and
+ * `int auto_increment` respectively
+ * - MySQL cannot index an unbounded TEXT, so keyed/indexed strings must be
+ * varchar; `shortText` exists for columns used as keys or in unique indexes
+ */
+export interface ColumnKit {
+ table: typeof sqlite.sqliteTable | typeof pg.pgTable | typeof mysql.mysqlTable;
+ /** Free-form string; unbounded where the engine allows it. */
+ text: (name: string) => AnyColumnBuilder;
+ /** String used as a key, unique or indexed — bounded so MySQL can index it. */
+ shortText: (name: string, length?: number) => AnyColumnBuilder;
+ int: (name: string) => AnyColumnBuilder;
+ /** Auto-incrementing surrogate primary key. */
+ serial: (name: string) => AnyColumnBuilder;
+ bool: (name: string) => AnyColumnBuilder;
+ real: (name: string) => AnyColumnBuilder;
+}
+
+// drizzle's builders are heavily generic; the schema modules keep their own
+// precise types, so this alias only exists to describe the kit's shape.
+type AnyColumnBuilder = ReturnType;
+
+const DEFAULT_KEY_LENGTH = 255;
+
+export const sqliteKit = {
+ table: sqlite.sqliteTable,
+ text: (name: string) => sqlite.text(name),
+ shortText: (name: string) => sqlite.text(name),
+ int: (name: string) => sqlite.integer(name),
+ serial: (name: string) =>
+ sqlite.integer(name).primaryKey({ autoIncrement: true }),
+ bool: (name: string) => sqlite.integer(name, { mode: "boolean" }),
+ real: (name: string) => sqlite.real(name),
+} as const;
+
+export const pgKit = {
+ table: pg.pgTable,
+ text: (name: string) => pg.text(name),
+ shortText: (name: string, length = DEFAULT_KEY_LENGTH) =>
+ pg.varchar(name, { length }),
+ int: (name: string) => pg.integer(name),
+ serial: (name: string) => pg.serial(name).primaryKey(),
+ bool: (name: string) => pg.boolean(name),
+ real: (name: string) => pg.doublePrecision(name),
+} as const;
+
+export const mysqlKit = {
+ table: mysql.mysqlTable,
+ text: (name: string) => mysql.text(name),
+ shortText: (name: string, length = DEFAULT_KEY_LENGTH) =>
+ mysql.varchar(name, { length }),
+ int: (name: string) => mysql.int(name),
+ serial: (name: string) => mysql.int(name).autoincrement().primaryKey(),
+ bool: (name: string) => mysql.boolean(name),
+ real: (name: string) => mysql.double(name),
+} as const;
diff --git a/src/backend/database/db/connect.ts b/src/backend/database/db/connect.ts
new file mode 100644
index 00000000..db35b207
--- /dev/null
+++ b/src/backend/database/db/connect.ts
@@ -0,0 +1,74 @@
+import type { DatabaseDialect } from "./dialect.js";
+import type { PortableDatabase } from "../repositories/database-context.js";
+
+export const DATABASE_URL_ENV = "DATABASE_URL";
+
+/**
+ * Opens a connection to a client-server engine.
+ *
+ * SQLite is not handled here — it has its own lifecycle in db/index.ts, where
+ * the database is decrypted into memory and serialised back to a file. This
+ * covers the engines that connect to something already running.
+ *
+ * The returned handle is typed as PortableDatabase; see the note there on why
+ * that is an approximation and what guarantees it.
+ */
+export function databaseUrl(env: NodeJS.ProcessEnv = process.env): string | null {
+ const url = env[DATABASE_URL_ENV]?.trim();
+ return url ? url : null;
+}
+
+/**
+ * Checks the connection string suits the configured engine before trying to
+ * open it, so a mismatch fails with something readable rather than a driver
+ * error thirty frames down.
+ */
+export function assertUrlMatchesDialect(
+ url: string,
+ dialect: DatabaseDialect,
+): void {
+ const scheme = url.split("://", 1)[0].toLowerCase();
+
+ const expected: Record = {
+ postgres: ["postgres", "postgresql"],
+ mysql: ["mysql", "mariadb"],
+ };
+
+ const allowed = expected[dialect];
+ if (!allowed) {
+ throw new Error(`${dialect} does not use ${DATABASE_URL_ENV}`);
+ }
+
+ if (!allowed.includes(scheme)) {
+ throw new Error(
+ `${DATABASE_URL_ENV} is a "${scheme}://" URL but DATABASE_DIALECT is "${dialect}". ` +
+ `Expected one of ${allowed.map((s) => `${s}://`).join(", ")}.`,
+ );
+ }
+}
+
+export async function connectRemoteDatabase(
+ dialect: DatabaseDialect,
+ env: NodeJS.ProcessEnv = process.env,
+): Promise {
+ const url = databaseUrl(env);
+ if (!url) {
+ throw new Error(
+ `${DATABASE_URL_ENV} must be set when DATABASE_DIALECT is "${dialect}".`,
+ );
+ }
+
+ assertUrlMatchesDialect(url, dialect);
+
+ // No `schema` option: it only feeds drizzle's relational query API
+ // (`db.query.*`), which nothing here uses. The query builder takes its table
+ // names and value encoders from the table objects the repositories import —
+ // see the note in schema.pg.ts on why the generated schemas are DDL-only.
+ if (dialect === "postgres") {
+ const { drizzle } = await import("drizzle-orm/node-postgres");
+ return drizzle(url) as unknown as PortableDatabase;
+ }
+
+ const { drizzle } = await import("drizzle-orm/mysql2");
+ return drizzle(url) as unknown as PortableDatabase;
+}
diff --git a/src/backend/database/db/dialect.ts b/src/backend/database/db/dialect.ts
new file mode 100644
index 00000000..171017b5
--- /dev/null
+++ b/src/backend/database/db/dialect.ts
@@ -0,0 +1,50 @@
+/**
+ * Which engine the schema and repositories are built against.
+ *
+ * SQLite is not going away: the desktop app embeds its backend and cannot ship
+ * a database server, so it will always run on SQLite. Postgres and MySQL are
+ * for self-hosted deployments that need more than one process to reach the
+ * data. This is a multi-backend story, not a migration off SQLite.
+ */
+export type DatabaseDialect = "sqlite" | "postgres" | "mysql";
+
+export const DATABASE_DIALECT_ENV = "DATABASE_DIALECT";
+
+const SUPPORTED: readonly DatabaseDialect[] = ["sqlite", "postgres", "mysql"];
+
+export function isDatabaseDialect(value: unknown): value is DatabaseDialect {
+ return (
+ typeof value === "string" &&
+ (SUPPORTED as readonly string[]).includes(value)
+ );
+}
+
+/**
+ * Resolves the configured dialect, defaulting to SQLite so existing
+ * deployments and the desktop build are unaffected by this being added.
+ */
+export function resolveDatabaseDialect(
+ env: NodeJS.ProcessEnv = process.env,
+): DatabaseDialect {
+ const raw = env[DATABASE_DIALECT_ENV]?.trim().toLowerCase();
+ if (!raw) return "sqlite";
+
+ if (!isDatabaseDialect(raw)) {
+ throw new Error(
+ `Unsupported ${DATABASE_DIALECT_ENV}: "${raw}". Expected one of ${SUPPORTED.join(", ")}.`,
+ );
+ }
+ return raw;
+}
+
+/**
+ * Whether a write has to be explicitly persisted after it commits.
+ *
+ * SQLite here is an in-memory database serialised back to an encrypted file, so
+ * every write needs a trigger to flush it. Client-server engines have already
+ * durably committed by the time the query returns — there is no file to write
+ * and nothing to schedule.
+ */
+export function needsExplicitPersist(dialect: DatabaseDialect): boolean {
+ return dialect === "sqlite";
+}
diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts
index b3f0f97b..3317260b 100644
--- a/src/backend/database/db/index.ts
+++ b/src/backend/database/db/index.ts
@@ -7,8 +7,21 @@ import { databaseLogger } from "../../utils/logger.js";
import { DatabaseFileEncryption } from "../../utils/database-file-encryption.js";
import { SystemCrypto } from "../../utils/system-crypto.js";
import { DatabaseMigration } from "../../utils/database-migration.js";
+import {
+ ensureSharedHostAuthOverrideProtocolSchema,
+ migrateLegacySharedHostAuthOverrides,
+} from "../../utils/shared-host-auth-override-migration.js";
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
+import { migrateAuditRetention } from "../../utils/audit-retention-migration.js";
+import {
+ assertDataDirIsNotMisconfigured,
+ DataDirMisconfiguredError,
+} from "../../utils/data-dir-guard.js";
import { getDefaultGuacdUrl } from "../../utils/guacd-config.js";
+import { resolveDatabaseDialect, type DatabaseDialect } from "./dialect.js";
+import { connectRemoteDatabase } from "./connect.js";
+import { runRemoteMigrations } from "./migrate.js";
+import type { PortableDatabase } from "../repositories/database-context.js";
const dataDir = process.env.DATA_DIR || "./db/data";
const dbDir = path.resolve(dataDir);
@@ -104,11 +117,16 @@ async function initializeDatabaseAsync(): Promise {
);
}
} else {
+ assertDataDirIsNotMisconfigured(dataDir);
memoryDatabase = new Database(":memory:");
isNewDatabase = true;
}
}
} catch (error) {
+ // Not a decryption problem: the database is fine, we are pointed at the
+ // wrong directory. Surface that message as-is.
+ if (error instanceof DataDirMisconfiguredError) throw error;
+
databaseLogger.error("Failed to initialize memory database", error, {
operation: "db_memory_init_failed",
errorMessage: error instanceof Error ? error.message : "Unknown error",
@@ -145,8 +163,35 @@ async function initializeDatabaseAsync(): Promise {
);
}
} else {
- memoryDatabase = new Database(":memory:");
- isNewDatabase = true;
+ assertDataDirIsNotMisconfigured(dataDir);
+
+ // The database still lives in memory and is serialised out on every write;
+ // turning encryption off only changes whether that file is ciphertext. It
+ // has to be read back, or each restart starts empty and silently discards
+ // everything the previous run saved.
+ const existing = readPlainDatabaseFile();
+ if (existing) {
+ memoryDatabase = new Database(existing);
+ databaseLogger.info("Loaded unencrypted database from disk", {
+ operation: "db_load_plain",
+ path: dbPath,
+ bytes: existing.length,
+ });
+ } else {
+ memoryDatabase = new Database(":memory:");
+ isNewDatabase = true;
+ }
+ }
+}
+
+/** The plain database file, or null when there is nothing to restore. */
+function readPlainDatabaseFile(): Buffer | null {
+ try {
+ const contents = fs.readFileSync(dbPath);
+ return contents.length > 0 ? contents : null;
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
+ throw error;
}
}
@@ -472,13 +517,14 @@ async function initializeCompleteDatabase(): Promise {
success INTEGER NOT NULL,
error_message TEXT,
timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS session_recordings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host_id INTEGER NOT NULL,
- user_id TEXT NOT NULL,
+ user_id TEXT,
+ username TEXT,
access_id INTEGER,
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
ended_at TEXT,
@@ -491,7 +537,7 @@ async function initializeCompleteDatabase(): Promise {
terminated_by_owner INTEGER DEFAULT 0,
termination_reason TEXT,
FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
FOREIGN KEY (access_id) REFERENCES host_access (id) ON DELETE SET NULL
);
@@ -1418,6 +1464,28 @@ const migrateSchema = () => {
}
}
+ try {
+ ensureSharedHostAuthOverrideProtocolSchema(sqlite);
+ } catch (schemaError) {
+ databaseLogger.warn("Failed to prepare shared_host_auth_overrides table", {
+ operation: "schema_migration",
+ error: schemaError,
+ });
+ }
+
+ try {
+ migrateLegacySharedHostAuthOverrides(
+ sqlite,
+ getRawSettingValue,
+ setRawSettingValue,
+ );
+ } catch (migrateError) {
+ databaseLogger.warn("Failed to migrate shared host auth overrides", {
+ operation: "schema_migration",
+ error: migrateError,
+ });
+ }
+
try {
sqlite.prepare("SELECT credential_id FROM ssh_folders LIMIT 1").get();
} catch {
@@ -1448,6 +1516,7 @@ const migrateSchema = () => {
{ column: "connection_type", sql: "ALTER TABLE ssh_data ADD COLUMN connection_type TEXT NOT NULL DEFAULT 'ssh'" },
{ column: "credential_id", sql: "ALTER TABLE ssh_data ADD COLUMN credential_id INTEGER" },
{ column: "override_credential_username", sql: "ALTER TABLE ssh_data ADD COLUMN override_credential_username INTEGER" },
+ { column: "share_ssh_auth", sql: "ALTER TABLE ssh_data ADD COLUMN share_ssh_auth INTEGER NOT NULL DEFAULT 0" },
{ column: "jump_hosts", sql: "ALTER TABLE ssh_data ADD COLUMN jump_hosts TEXT" },
{ column: "show_terminal_in_sidebar", sql: "ALTER TABLE ssh_data ADD COLUMN show_terminal_in_sidebar INTEGER NOT NULL DEFAULT 1" },
{ column: "show_file_manager_in_sidebar", sql: "ALTER TABLE ssh_data ADD COLUMN show_file_manager_in_sidebar INTEGER NOT NULL DEFAULT 0" },
@@ -1637,7 +1706,7 @@ const migrateSchema = () => {
sqlite.exec(`
CREATE TABLE IF NOT EXISTS audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id TEXT NOT NULL,
+ user_id TEXT,
username TEXT NOT NULL,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
@@ -1649,7 +1718,7 @@ const migrateSchema = () => {
success INTEGER NOT NULL,
error_message TEXT,
timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
);
`);
} catch (createError) {
@@ -2497,6 +2566,10 @@ const migrateSchema = () => {
}
// --- sync end ---
+ // Audit trails and session recordings used to be deleted along with the user
+ // they referenced, which defeats the point of keeping them.
+ migrateAuditRetention(sqlite);
+
databaseLogger.success("Schema migration completed", {
operation: "schema_migration",
});
@@ -2580,10 +2653,54 @@ async function handlePostInitFileEncryption() {
}
async function initializeDatabase(): Promise {
+ const dialect = resolveDatabaseDialect();
+
+ if (dialect !== "sqlite") {
+ await initializeRemoteDatabase(dialect);
+ return;
+ }
+
await initializeCompleteDatabase();
await handlePostInitFileEncryption();
}
+/**
+ * Startup against Postgres or MySQL.
+ *
+ * Shorter than the SQLite path because most of what that one does has no
+ * counterpart here: there is no file to decrypt, no in-memory copy to keep in
+ * step with disk, and the schema comes from drizzle-kit migrations instead of
+ * the inline DDL below.
+ *
+ * What does carry over is the settings cache. 27 call sites read settings
+ * synchronously, which better-sqlite3 allows and no remote driver does, so the
+ * table is loaded once here before anything asks for it.
+ */
+async function initializeRemoteDatabase(
+ dialect: Exclude,
+): Promise {
+ databaseLogger.info(`Connecting to ${dialect} database`, {
+ operation: "db_init",
+ dialect,
+ });
+
+ db = await connectRemoteDatabase(dialect);
+ await runRemoteMigrations(dialect, db);
+
+ // Imported here rather than at the top: factory.ts imports getDb from this
+ // module, and a static import would close the cycle at module-load time.
+ const { primeCurrentSettingsCache, startSettingsCacheRefresh } = await import(
+ "../repositories/factory.js"
+ );
+ await primeCurrentSettingsCache();
+ startSettingsCacheRefresh();
+
+ databaseLogger.info(`${dialect} database ready`, {
+ operation: "db_init_complete",
+ dialect,
+ });
+}
+
export { initializeDatabase };
async function cleanupDatabase() {
@@ -2661,9 +2778,9 @@ process.on("SIGTERM", async () => {
process.exit(0);
});
-let db: ReturnType>;
+let db: PortableDatabase;
-export function getDb(): ReturnType> {
+export function getDb(): PortableDatabase {
if (!db) {
throw new Error(
"Database not initialized. Ensure initializeDatabase() is called before accessing db.",
@@ -2674,6 +2791,13 @@ export function getDb(): ReturnType> {
export function getSqlite(): Database.Database {
if (!sqlite) {
+ const dialect = resolveDatabaseDialect();
+ if (dialect !== "sqlite") {
+ throw new Error(
+ `No SQLite handle: DATABASE_DIALECT is "${dialect}". This caller needs a ` +
+ `synchronous query, which only SQLite offers — give it an async path instead.`,
+ );
+ }
throw new Error(
"SQLite not initialized. Ensure initializeDatabase() is called before accessing sqlite.",
);
diff --git a/src/backend/database/db/migrate.ts b/src/backend/database/db/migrate.ts
new file mode 100644
index 00000000..57d99912
--- /dev/null
+++ b/src/backend/database/db/migrate.ts
@@ -0,0 +1,51 @@
+import path from "path";
+import type { DatabaseDialect } from "./dialect.js";
+import type { PortableDatabase } from "../repositories/database-context.js";
+
+export const MIGRATIONS_DIR_ENV = "DRIZZLE_MIGRATIONS_DIR";
+
+/**
+ * Where the generated migrations live.
+ *
+ * SQLite does not appear here: it builds its schema from the DDL in index.ts
+ * and patches it forward with migrateSchema(). Only the client-server engines
+ * use drizzle-kit migrations, and each has its own folder because the
+ * generated SQL differs per dialect.
+ */
+export function migrationsFolder(
+ dialect: DatabaseDialect,
+ env: NodeJS.ProcessEnv = process.env,
+): string {
+ const override = env[MIGRATIONS_DIR_ENV]?.trim();
+ const root = override || path.resolve(process.cwd(), "drizzle");
+ return path.join(root, dialect);
+}
+
+/**
+ * Brings a remote database up to the current schema.
+ *
+ * drizzle's migrator records what it has applied in its own table, so this is
+ * safe to run on every start — including against a database another instance
+ * already migrated.
+ */
+export async function runRemoteMigrations(
+ dialect: DatabaseDialect,
+ db: PortableDatabase,
+ env: NodeJS.ProcessEnv = process.env,
+): Promise {
+ if (dialect === "sqlite") {
+ throw new Error("SQLite builds its schema in index.ts, not from drizzle/");
+ }
+
+ const folder = migrationsFolder(dialect, env);
+
+ const { migrate } =
+ dialect === "postgres"
+ ? await import("drizzle-orm/node-postgres/migrator")
+ : await import("drizzle-orm/mysql2/migrator");
+
+ await (migrate as (db: unknown, config: { migrationsFolder: string }) => Promise)(
+ db,
+ { migrationsFolder: folder },
+ );
+}
diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts
new file mode 100644
index 00000000..b86c18c2
--- /dev/null
+++ b/src/backend/database/db/schema.mysql.ts
@@ -0,0 +1,1283 @@
+// GENERATED FILE — do not edit.
+//
+// Produced from schema.ts by scripts/generate-dialect-schema.cjs.
+// Edit the sqlite schema and re-run `node scripts/generate-dialect-schema.cjs`.
+// Target dialect: mysql.
+//
+// DDL source for drizzle-kit. NOT imported to run queries — repositories use
+// schema.ts on every dialect. See the generator header for why that is correct.
+
+import {
+ mysqlTable,
+ text,
+ varchar,
+ int,
+ boolean,
+ double,
+ uniqueIndex,
+} from "drizzle-orm/mysql-core";
+import { sql } from "drizzle-orm";
+
+export const users = mysqlTable("users", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ username: text("username").notNull(),
+ passwordHash: text("password_hash").notNull(),
+ isAdmin: boolean("is_admin").notNull().default(false),
+
+ isOidc: boolean("is_oidc").notNull().default(false),
+ oidcIdentifier: text("oidc_identifier"),
+ ssoProviderId: int("sso_provider_id"),
+ clientId: text("client_id"),
+ clientSecret: text("client_secret"),
+ issuerUrl: text("issuer_url"),
+ authorizationUrl: text("authorization_url"),
+ tokenUrl: text("token_url"),
+ identifierPath: text("identifier_path"),
+ namePath: text("name_path"),
+ scopes: text().default("openid email profile"),
+
+ totpSecret: text("totp_secret"),
+ totpEnabled: boolean("totp_enabled")
+ .notNull()
+ .default(false),
+ totpBackupCodes: text("totp_backup_codes"),
+
+ registeredAt: text("registered_at").notNull().default(sql`(CURRENT_TIMESTAMP)`),
+ donationModalDismissed: boolean("donation_modal_dismissed")
+ .notNull()
+ .default(false),
+});
+
+export const settings = mysqlTable("settings", {
+ key: varchar("key", { length: 255 }).primaryKey(),
+ value: text("value").notNull(),
+});
+
+export const ssoProviders = mysqlTable("sso_providers", {
+ id: int("id").autoincrement().primaryKey(),
+ name: varchar("name", { length: 255 }).notNull(),
+ type: text("type").notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ displayOrder: int("display_order").notNull().default(0),
+ config: text("config").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const sessions = mysqlTable("sessions", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ jwtToken: text("jwt_token").notNull(),
+ deviceType: text("device_type").notNull(),
+ deviceInfo: text("device_info").notNull(),
+ oidcSub: text("oidc_sub"),
+ oidcSid: text("oidc_sid"),
+ ssoProviderId: int("sso_provider_id"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at").notNull(),
+ lastActiveAt: text("last_active_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const trustedDevices = mysqlTable("trusted_devices", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ deviceFingerprint: text("device_fingerprint").notNull(),
+ deviceType: text("device_type").notNull(),
+ deviceInfo: text("device_info").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsedAt: text("last_used_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const webauthnCredentials = mysqlTable("webauthn_credentials", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ credentialId: text("credential_id").notNull(),
+ publicKey: text("public_key").notNull(),
+ counter: int("counter").notNull().default(0),
+ deviceType: text("device_type"),
+ backedUp: boolean("backed_up").notNull().default(false),
+ transports: text("transports"),
+ userVerification: text("user_verification").notNull().default("preferred"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ lastUsedAt: text("last_used_at"),
+});
+
+export const hosts = mysqlTable("ssh_data", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ connectionType: text("connection_type").notNull().default("ssh"),
+ name: varchar("name", { length: 255 }),
+ ip: text("ip").notNull(),
+ port: int("port").notNull(),
+ username: text("username").notNull(),
+ folder: text("folder"),
+ tags: text("tags"),
+ pin: boolean("pin").notNull().default(false),
+ authType: text("auth_type").notNull(),
+ useWarpgate: boolean("use_warpgate").notNull().default(false),
+ shareSshAuth: boolean("share_ssh_auth")
+ .notNull()
+ .default(false),
+ forceKeyboardInteractive: text("force_keyboard_interactive"),
+
+ password: text("password"),
+ key: text("key"),
+ keyPassword: text("key_password"),
+ keyType: text("key_type"),
+ sudoPassword: text("sudo_password"),
+
+ autostartPassword: text("autostart_password"),
+ autostartKey: text("autostart_key"),
+ autostartKeyPassword: text("autostart_key_password"),
+
+ credentialId: int("credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ overrideCredentialUsername: boolean("override_credential_username"),
+ // When authType is "vault", the host authenticates via a Vault SSH signer
+ // profile (shared settings, no secrets). The signing certificate is obtained
+ // per-user at connect time via an interactive Vault OIDC flow.
+ vaultProfileId: int("vault_profile_id").references(
+ () => vaultProfiles.id,
+ { onDelete: "set null" },
+ ),
+ enableTerminal: boolean("enable_terminal")
+ .notNull()
+ .default(true),
+ enableSessionLogging: boolean("enable_session_logging")
+ .notNull()
+ .default(true),
+ allowSessionSharing: boolean("allow_session_sharing")
+ .notNull()
+ .default(true),
+ enableCommandHistory: boolean("enable_command_history")
+ .notNull()
+ .default(true),
+ enableTunnel: boolean("enable_tunnel")
+ .notNull()
+ .default(true),
+ tunnelConnections: text("tunnel_connections"),
+ jumpHosts: text("jump_hosts"),
+ enableFileManager: boolean("enable_file_manager")
+ .notNull()
+ .default(true),
+ scpLegacy: boolean("scp_legacy").notNull().default(false),
+ enableDocker: boolean("enable_docker")
+ .notNull()
+ .default(false),
+ enableTmuxMonitor: boolean("enable_tmux_monitor")
+ .notNull()
+ .default(false),
+ showTerminalInSidebar: boolean("show_terminal_in_sidebar")
+ .notNull()
+ .default(true),
+ showFileManagerInSidebar: boolean("show_file_manager_in_sidebar")
+ .notNull()
+ .default(false),
+ showTunnelInSidebar: boolean("show_tunnel_in_sidebar")
+ .notNull()
+ .default(false),
+ showDockerInSidebar: boolean("show_docker_in_sidebar")
+ .notNull()
+ .default(false),
+ showServerStatsInSidebar: boolean("show_server_stats_in_sidebar")
+ .notNull()
+ .default(false),
+ defaultPath: text("default_path"),
+ statsConfig: text("stats_config"),
+ dockerConfig: text("docker_config"),
+ enableProxmox: boolean("enable_proxmox")
+ .notNull()
+ .default(false),
+ proxmoxConfig: text("proxmox_config"),
+ terminalConfig: text("terminal_config"),
+ quickActions: text("quick_actions"),
+ notes: text("notes"),
+ enableSsh: boolean("enable_ssh").notNull().default(true),
+ enableRdp: boolean("enable_rdp").notNull().default(false),
+ enableVnc: boolean("enable_vnc").notNull().default(false),
+ enableTelnet: boolean("enable_telnet").notNull().default(false),
+
+ sshPort: int("ssh_port").default(22),
+ rdpPort: int("rdp_port").default(3389),
+ vncPort: int("vnc_port").default(5900),
+ telnetPort: int("telnet_port").default(23),
+
+ rdpCredentialId: int("rdp_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ rdpUser: text("rdp_user"),
+ rdpPassword: text("rdp_password"),
+ rdpDomain: text("rdp_domain"),
+ rdpSecurity: text("rdp_security"),
+ rdpIgnoreCert: boolean("rdp_ignore_cert").default(false),
+
+ vncCredentialId: int("vnc_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ vncPassword: text("vnc_password"),
+ vncUser: text("vnc_user"),
+
+ telnetUser: text("telnet_user"),
+ telnetPassword: text("telnet_password"),
+ telnetCredentialId: int("telnet_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+
+ rdpAuthType: text("rdp_auth_type"),
+ vncAuthType: text("vnc_auth_type"),
+ telnetAuthType: text("telnet_auth_type"),
+
+ domain: text("domain"),
+ security: text("security"),
+ ignoreCert: boolean("ignore_cert").default(false),
+ guacamoleConfig: text("guacamole_config"),
+
+ useSocks5: boolean("use_socks5"),
+ socks5Host: text("socks5_host"),
+ socks5Port: int("socks5_port"),
+ socks5Username: text("socks5_username"),
+ socks5Password: text("socks5_password"),
+ socks5ProxyChain: text("socks5_proxy_chain"),
+
+ // null = use the desktop app's global default; "local" | "remote" pins
+ // this specific host's SSH/Docker-console/Serial connections to originate
+ // from the embedded local backend or a connected remote sync server.
+ // Ignored for rdp/vnc/telnet, which always require the remote server.
+ connectionOrigin: text("connection_origin"),
+
+ macAddress: text("mac_address"),
+ wolBroadcastAddress: text("wol_broadcast_address"),
+ portKnockSequence: text("port_knock_sequence"),
+
+ hostKeyFingerprint: text("host_key_fingerprint"),
+ hostKeyType: text("host_key_type"),
+ hostKeyAlgorithm: text("host_key_algorithm").default("sha256"),
+ hostKeyFirstSeen: text("host_key_first_seen"),
+ hostKeyLastVerified: text("host_key_last_verified"),
+ hostKeyChangedCount: int("host_key_changed_count").default(0),
+
+ // Stable identity used to match this row across two independently-seeded
+ // databases (the embedded backend and a connected remote server) during
+ // sync -- local autoincrement ids collide across instances.
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const fileManagerRecent = mysqlTable("file_manager_recent", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ lastOpened: text("last_opened")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const fileManagerPinned = mysqlTable("file_manager_pinned", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ pinnedAt: text("pinned_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const fileManagerShortcuts = mysqlTable("file_manager_shortcuts", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const transferRecent = mysqlTable("transfer_recent", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ sourceHostId: int("source_host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ destHostId: int("dest_host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ destPath: text("dest_path").notNull(),
+ destPathLabel: text("dest_path_label").notNull(),
+ lastUsed: text("last_used")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const dismissedAlerts = mysqlTable("dismissed_alerts", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ alertId: text("alert_id").notNull(),
+ dismissedAt: text("dismissed_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const sshCredentials = mysqlTable("ssh_credentials", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ tags: text("tags"),
+ authType: text("auth_type").notNull(),
+ username: text("username"),
+ password: text("password"),
+ key: text("key"),
+ privateKey: text("private_key"),
+ publicKey: text("public_key"),
+ keyPassword: text("key_password"),
+ keyType: text("key_type"),
+ detectedKeyType: text("detected_key_type"),
+
+ certPublicKey: text("cert_public_key"),
+
+
+ usageCount: int("usage_count").notNull().default(0),
+ lastUsed: text("last_used"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const sshCredentialUsage = mysqlTable("ssh_credential_usage", {
+ id: int("id").autoincrement().primaryKey(),
+ credentialId: int("credential_id")
+ .notNull()
+ .references(() => sshCredentials.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ usedAt: text("used_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const snippets = mysqlTable("snippets", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ content: text("content").notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ order: int("order").notNull().default(0),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ hostFilter: text("host_filter"),
+});
+
+export const snippetFolders = mysqlTable("snippet_folders", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ color: text("color"),
+ icon: text("icon"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const c2sTunnelPresets = mysqlTable("c2s_tunnel_presets", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ config: text("config").notNull(),
+ platform: text("platform"),
+ computerName: text("computer_name"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const snippetAccess = mysqlTable("snippet_access", {
+ id: int("id").autoincrement().primaryKey(),
+ snippetId: int("snippet_id")
+ .notNull()
+ .references(() => snippets.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "cascade" }),
+ roleId: int("role_id").references(() => roles.id, {
+ onDelete: "cascade",
+ }),
+
+ grantedBy: varchar("granted_by", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ permissionLevel: text("permission_level").notNull().default("view"),
+
+ expiresAt: text("expires_at"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const sshFolders = mysqlTable("ssh_folders", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ color: text("color"),
+ icon: text("icon"),
+ credentialId: int("credential_id").references(() => sshCredentials.id, {
+ onDelete: "set null",
+ }),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const recentActivity = mysqlTable("recent_activity", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ type: text("type").notNull(),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ hostName: text("host_name"),
+ timestamp: text("timestamp")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const commandHistory = mysqlTable("command_history", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ command: text("command").notNull(),
+ executedAt: text("executed_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const networkTopology = mysqlTable("network_topology", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ topology: text("topology"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const hostAccess = mysqlTable("host_access", {
+ id: int("id").autoincrement().primaryKey(),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 })
+ .references(() => users.id, { onDelete: "cascade" }),
+ roleId: int("role_id")
+ .references(() => roles.id, { onDelete: "cascade" }),
+
+ grantedBy: varchar("granted_by", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ permissionLevel: text("permission_level")
+ .notNull()
+ .default("connect"),
+
+ expiresAt: text("expires_at"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ lastAccessedAt: text("last_accessed_at"),
+ accessCount: int("access_count").notNull().default(0),
+});
+
+export const sharedHostAuthOverrides = mysqlTable(
+ "shared_host_auth_overrides",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ credentialId: int("credential_id")
+ .notNull()
+ .references(() => sshCredentials.id, { onDelete: "cascade" }),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ },
+ (table) => [
+ uniqueIndex("shared_host_auth_overrides_host_user_protocol_unique").on(
+ table.hostId,
+ table.userId,
+ table.protocol,
+ ),
+ ],
+);
+
+export const sharedHostSecrets = mysqlTable(
+ "shared_host_secrets",
+ {
+ id: int("id").autoincrement().primaryKey(),
+
+ hostAccessId: int("host_access_id")
+ .notNull()
+ .references(() => hostAccess.id, { onDelete: "cascade" }),
+
+ targetUserId: varchar("target_user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ sourceType: text("source_type").notNull().default("credential"),
+
+ originalCredentialId: int("original_credential_id").references(
+ () => sshCredentials.id,
+ { onDelete: "cascade" },
+ ),
+
+ encryptedUsername: text("encrypted_username"),
+ encryptedAuthType: text("encrypted_auth_type"),
+ encryptedPassword: text("encrypted_password"),
+ encryptedKey: text("encrypted_key"),
+ encryptedKeyPassword: text("encrypted_key_password"),
+ encryptedKeyType: text("encrypted_key_type"),
+ encryptedDomain: text("encrypted_domain"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_shared_host_secrets_scope").on(
+ table.hostAccessId,
+ table.targetUserId,
+ table.protocol,
+ ),
+ ],
+);
+
+export const roles = mysqlTable("roles", {
+ id: int("id").autoincrement().primaryKey(),
+ name: varchar("name", { length: 255 }).notNull().unique(),
+ displayName: text("display_name").notNull(),
+ description: text("description"),
+
+ isSystem: boolean("is_system")
+ .notNull()
+ .default(false),
+
+ permissions: text("permissions"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const userRoles = mysqlTable(
+ "user_roles",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ roleId: int("role_id")
+ .notNull()
+ .references(() => roles.id, { onDelete: "cascade" }),
+
+ grantedBy: varchar("granted_by", { length: 255 }).references(() => users.id, {
+ onDelete: "set null",
+ }),
+ grantedAt: text("granted_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_user_roles_user_role").on(table.userId, table.roleId)],
+);
+
+export const auditLogs = mysqlTable("audit_logs", {
+ id: int("id").autoincrement().primaryKey(),
+
+ // Nullable on purpose: the trail outlives the account, and username keeps the
+ // entry attributable once the reference is gone.
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }),
+ username: text("username").notNull(),
+
+ action: text("action").notNull(),
+ resourceType: text("resource_type").notNull(),
+ resourceId: text("resource_id"),
+ resourceName: text("resource_name"),
+
+ details: text("details"),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
+
+ success: boolean("success").notNull(),
+ errorMessage: text("error_message"),
+
+ timestamp: text("timestamp")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const sessionRecordings = mysqlTable("session_recordings", {
+ id: int("id").autoincrement().primaryKey(),
+
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // Nullable on purpose: a recording is evidence about the host as much as the
+ // person, so it outlives the account. username keeps it attributable.
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }),
+ username: text("username"),
+ accessId: int("access_id").references(() => hostAccess.id, {
+ onDelete: "set null",
+ }),
+
+ startedAt: text("started_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ endedAt: text("ended_at"),
+ duration: int("duration"),
+
+ commands: text("commands"),
+ dangerousActions: text("dangerous_actions"),
+
+ recordingPath: text("recording_path"),
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ format: text("format").notNull().default("text"),
+
+ terminatedByOwner: boolean("terminated_by_owner")
+ .default(false),
+ terminationReason: text("termination_reason"),
+});
+
+export const sessionShares = mysqlTable("session_shares", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ ownerUserId: varchar("owner_user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ protocol: varchar("protocol", { length: 255 }).notNull(),
+
+ // Live-session binding: TerminalSessionManager's session.id for SSH, or
+ // guacd's own guacamoleConnectionId for rdp/vnc/telnet. Neither is a DB
+ // row (process-local, in-memory) so this intentionally has no FK.
+ sessionId: text("session_id").notNull(),
+ tabInstanceId: text("tab_instance_id"),
+
+ shareType: text("share_type").notNull(), // "link" | "user"
+ targetUserId: varchar("target_user_id", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ linkToken: varchar("link_token", { length: 255 }).unique(),
+
+ permissionLevel: text("permission_level").notNull().default("read-only"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at").notNull(),
+ revokedAt: text("revoked_at"),
+
+ lastJoinedAt: text("last_joined_at"),
+ joinCount: int("join_count").notNull().default(0),
+});
+
+export const sessionShareParticipants = mysqlTable(
+ "session_share_participants",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ shareId: varchar("share_id", { length: 255 })
+ .notNull()
+ .references(() => sessionShares.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ guestLabel: text("guest_label"),
+
+ joinedAt: text("joined_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ leftAt: text("left_at"),
+ },
+);
+
+export const opksshTokens = mysqlTable(
+ "opkssh_tokens",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert").notNull(),
+ privateKey: text("private_key").notNull(),
+
+ email: text("email"),
+ sub: text("sub"),
+ issuer: text("issuer"),
+ audience: text("audience"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_opkssh_tokens_user_host").on(table.userId, table.hostId)],
+);
+
+// Vault SSH signer profiles. These hold ONLY non-secret connection settings and
+// are intended to be shared across users (shared === true makes a profile
+// visible to every user on the server). Each user authenticates to Vault via an
+// interactive OIDC flow at connect time; no tokens or keys are stored here.
+export const vaultProfiles = mysqlTable("vault_profiles", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ tags: text("tags"),
+ // Vault server connection (non-secret)
+ vaultAddr: text("vault_addr").notNull(),
+ vaultNamespace: text("vault_namespace"),
+ // OIDC auth method mount + role used to obtain a Vault token interactively
+ oidcMount: text("oidc_mount"),
+ oidcRole: text("oidc_role"),
+ // SSH secrets engine mount + signer role used to sign the ephemeral key
+ sshMount: text("ssh_mount"),
+ sshRole: text("ssh_role").notNull(),
+ validPrincipals: text("valid_principals"),
+ // Ephemeral keypair algorithm to generate per connection
+ keyType: text("key_type"),
+ // When true the profile is visible/usable by all users on the server
+ shared: boolean("shared").notNull().default(false),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+// Per-user cache of the ephemeral SSH private key + Vault-signed certificate.
+// Transient: rows live only until the certificate expires. Secret fields are
+// encrypted under the user's data-encryption key (see field-crypto.ts).
+export const vaultTokens = mysqlTable(
+ "vault_tokens",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ profileId: int("profile_id")
+ .notNull()
+ .references(() => vaultProfiles.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert").notNull(),
+ privateKey: text("private_key").notNull(),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)],
+);
+
+export const apiKeys = mysqlTable("api_keys", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ tokenHash: text("token_hash").notNull(),
+ tokenPrefix: text("token_prefix").notNull(),
+ createdAt: text("created_at").notNull().default(sql`(CURRENT_TIMESTAMP)`),
+ expiresAt: text("expires_at"),
+ lastUsedAt: text("last_used_at"),
+ isActive: boolean("is_active").notNull().default(true),
+});
+
+export const userOpenTabs = mysqlTable("user_open_tabs", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ tabType: text("tab_type").notNull(),
+ hostId: int("host_id").references(() => hosts.id, { onDelete: "cascade" }),
+ label: text("label").notNull(),
+ tabOrder: int("tab_order").notNull().default(0),
+ backendSessionId: text("backend_session_id"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const userPreferences = mysqlTable("user_preferences", {
+ userId: varchar("user_id", { length: 255 })
+ .primaryKey()
+ .references(() => users.id, { onDelete: "cascade" }),
+ reopenTabsOnLogin: boolean("reopen_tabs_on_login")
+ .notNull()
+ .default(false),
+ theme: text("theme"),
+ fontSize: text("font_size"),
+ accentColor: text("accent_color"),
+ language: text("language"),
+ storageMode: text("storage_mode"),
+ commandAutocomplete: boolean("command_autocomplete"),
+ commandPaletteEnabled: boolean("command_palette_enabled"),
+ showHostTags: boolean("show_host_tags"),
+ hostTrayOnClick: boolean("host_tray_on_click"),
+ pinAppRail: boolean("pin_app_rail"),
+ expandAppRailOnHover: boolean("expand_app_rail_on_hover"),
+ foldersCollapsed: boolean("folders_collapsed"),
+ confirmSnippetExecution: boolean("confirm_snippet_execution"),
+ disableUpdateCheck: boolean("disable_update_check"),
+ confirmTabClose: boolean("confirm_tab_close"),
+ hiddenRailTabs: text("hidden_rail_tabs"),
+ compactHostView: boolean("compact_host_view"),
+ statusColorScheme: text("status_color_scheme"),
+ customThemes: text("custom_themes"),
+ customKeybindings: text("custom_keybindings"),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const hostMetricsPreferences = mysqlTable(
+ "host_metrics_preferences",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // JSON-encoded HostMetricsLayout. Layout has no secrets, so it is stored as
+ // plain JSON (no field-level encryption).
+ layout: text("layout").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ },
+ // One layout per user per host. Enforced in production since the inline DDL
+ // creates it, but it was never declared here, so the generated Postgres and
+ // MySQL schemas lacked it — and the upsert has nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_host_metrics_prefs_user_host").on(table.userId, table.hostId),
+ ],
+);
+
+export const hostHealthChecks = mysqlTable(
+ "host_health_checks",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // JSON array of { id, name, type: "tcp"|"http", target, port, path }
+ checks: text("checks").notNull(),
+ intervalSeconds: int("interval_seconds").notNull().default(300),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ },
+ // Same as above: one set of checks per user per host.
+ (table) => [
+ uniqueIndex("idx_host_health_checks_user_host").on(table.userId, table.hostId),
+ ],
+);
+
+export const hostHealthHistory = mysqlTable("host_health_history", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ checkId: text("check_id").notNull(),
+ ts: text("ts").notNull().default(sql`(CURRENT_TIMESTAMP)`),
+ ok: boolean("ok").notNull(),
+ latencyMs: int("latency_ms"),
+ detail: text("detail"),
+});
+
+export const dashboardServiceLinks = mysqlTable("dashboard_service_links", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ label: text("label").notNull(),
+ url: text("url").notNull(),
+ order: int("order").notNull().default(0),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+// --- termix-id begin ---
+// A user claims a unique public handle. Their published SSH public keys are
+// served at an unauthenticated resolver endpoint in authorized_keys format,
+// so any server can be provisioned with `curl /termix-id/u/ >> ~/.ssh/authorized_keys`.
+export const termixIdentities = mysqlTable("termix_identities", {
+ id: int("id").autoincrement().primaryKey(),
+ // One Termix ID per user — enforced in schema, not just in code.
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .unique()
+ .references(() => users.id, { onDelete: "cascade" }),
+ handle: varchar("handle", { length: 255 }).notNull().unique(),
+ description: text("description"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const termixIdentityKeys = mysqlTable("termix_identity_keys", {
+ id: int("id").autoincrement().primaryKey(),
+ identityId: int("identity_id")
+ .notNull()
+ .references(() => termixIdentities.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // Public keys are non-secret, so they are stored in plaintext (no field-level
+ // encryption). This is what lets the unauthenticated resolver serve them.
+ publicKey: text("public_key").notNull(),
+ // Raw algorithm token (e.g. "ssh-ed25519"), and a normalized group used for
+ // the / resolver filter (RSA / ED25519 / ECDSA / ...).
+ keyType: text("key_type").notNull(),
+ algorithm: text("algorithm").notNull(),
+ label: text("label"),
+ comment: text("comment"),
+ // "manual" (pasted) or "credential" (imported from an ssh_credentials entry).
+ source: text("source").notNull().default("manual"),
+ credentialId: int("credential_id").references(() => sshCredentials.id, {
+ onDelete: "set null",
+ }),
+ enabled: boolean("enabled").notNull().default(true),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+// Per-identity certificate authority. Servers that trust this CA (via
+// TrustedUserCAKeys / @cert-authority) accept any user certificate it signs,
+// giving central revocation (rotate the CA) and expiry (cert validity).
+export const termixIdentityCa = mysqlTable("termix_identity_ca", {
+ id: int("id").autoincrement().primaryKey(),
+ identityId: int("identity_id")
+ .notNull()
+ .unique()
+ .references(() => termixIdentities.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // CA public key (plaintext — it is published); CA private key is field-encrypted.
+ publicKey: text("public_key").notNull(),
+ privateKey: text("private_key").notNull(),
+ validityDays: int("validity_days").notNull().default(90),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+// --- termix-id end ---
+
+// --- tmux-monitor begin ---
+export const tmuxSessionTags = mysqlTable("tmux_session_tags", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ sessionName: text("session_name").notNull(),
+ tag: text("tag").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+// --- tmux-monitor end ---
+
+// --- metrics-history begin ---
+export const hostMetricsHistory = mysqlTable("host_metrics_history", {
+ id: int("id").autoincrement().primaryKey(),
+ hostId: int("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ ts: text("ts")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ cpuPercent: double("cpu_percent"),
+ memPercent: double("mem_percent"),
+ diskPercent: double("disk_percent"),
+ netRxBytes: int("net_rx_bytes"),
+ netTxBytes: int("net_tx_bytes"),
+});
+// --- metrics-history end ---
+
+// --- alerts begin ---
+export const alertRules = mysqlTable("alert_rules", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: int("host_id").references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ triggerType: text("trigger_type").notNull(),
+ thresholdValue: double("threshold_value"),
+ thresholdDurationSeconds: int("threshold_duration_seconds"),
+ cooldownMinutes: int("cooldown_minutes").notNull().default(15),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const notificationChannels = mysqlTable("notification_channels", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ type: text("type").notNull(),
+ config: text("config").notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const alertRuleChannels = mysqlTable("alert_rule_channels", {
+ id: int("id").autoincrement().primaryKey(),
+ ruleId: int("rule_id")
+ .notNull()
+ .references(() => alertRules.id, { onDelete: "cascade" }),
+ channelId: int("channel_id")
+ .notNull()
+ .references(() => notificationChannels.id, { onDelete: "cascade" }),
+});
+
+export const alertFirings = mysqlTable("alert_firings", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ ruleId: int("rule_id")
+ .notNull()
+ .references(() => alertRules.id, { onDelete: "cascade" }),
+ hostId: int("host_id").notNull(),
+ hostName: text("host_name").notNull(),
+ firedAt: text("fired_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ resolvedAt: text("resolved_at"),
+ value: double("value"),
+ message: text("message").notNull(),
+ severity: text("severity").notNull().default("warning"),
+ acknowledged: boolean("acknowledged").notNull().default(false),
+});
+// --- alerts end ---
+
+// --- homepage begin ---
+export const homepageItems = mysqlTable("homepage_items", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ typeId: text("type_id").notNull(),
+ title: text("title"),
+ config: text("config").notNull().default("{}"),
+ folderId: int("folder_id"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+
+export const homepageLayouts = mysqlTable("homepage_layouts", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .unique()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // JSON: { entries: HomepageLayoutEntry[], pan: {x,y}, zoom: number }
+ layout: text("layout").notNull().default("{}"),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+// --- homepage end ---
+
+// --- sync begin ---
+// Records a delete for a synced entity type so the other side of a sync
+// pair (embedded desktop backend <-> connected remote server) learns about
+// the deletion instead of re-creating the row on its next pull.
+export const syncTombstones = mysqlTable("sync_tombstones", {
+ id: int("id").autoincrement().primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ entityType: text("entity_type").notNull(),
+ syncId: varchar("sync_id", { length: 255 }).notNull(),
+ deletedAt: text("deleted_at")
+ .notNull()
+ .default(sql`(CURRENT_TIMESTAMP)`),
+});
+// --- sync end ---
diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts
new file mode 100644
index 00000000..1549fc86
--- /dev/null
+++ b/src/backend/database/db/schema.pg.ts
@@ -0,0 +1,1284 @@
+// GENERATED FILE — do not edit.
+//
+// Produced from schema.ts by scripts/generate-dialect-schema.cjs.
+// Edit the sqlite schema and re-run `node scripts/generate-dialect-schema.cjs`.
+// Target dialect: postgres.
+//
+// DDL source for drizzle-kit. NOT imported to run queries — repositories use
+// schema.ts on every dialect. See the generator header for why that is correct.
+
+import {
+ pgTable,
+ text,
+ varchar,
+ integer,
+ serial,
+ boolean,
+ doublePrecision,
+ uniqueIndex,
+} from "drizzle-orm/pg-core";
+import { sql } from "drizzle-orm";
+
+export const users = pgTable("users", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ username: text("username").notNull(),
+ passwordHash: text("password_hash").notNull(),
+ isAdmin: boolean("is_admin").notNull().default(false),
+
+ isOidc: boolean("is_oidc").notNull().default(false),
+ oidcIdentifier: text("oidc_identifier"),
+ ssoProviderId: integer("sso_provider_id"),
+ clientId: text("client_id"),
+ clientSecret: text("client_secret"),
+ issuerUrl: text("issuer_url"),
+ authorizationUrl: text("authorization_url"),
+ tokenUrl: text("token_url"),
+ identifierPath: text("identifier_path"),
+ namePath: text("name_path"),
+ scopes: text().default("openid email profile"),
+
+ totpSecret: text("totp_secret"),
+ totpEnabled: boolean("totp_enabled")
+ .notNull()
+ .default(false),
+ totpBackupCodes: text("totp_backup_codes"),
+
+ registeredAt: text("registered_at").notNull().default(sql`CURRENT_TIMESTAMP`),
+ donationModalDismissed: boolean("donation_modal_dismissed")
+ .notNull()
+ .default(false),
+});
+
+export const settings = pgTable("settings", {
+ key: varchar("key", { length: 255 }).primaryKey(),
+ value: text("value").notNull(),
+});
+
+export const ssoProviders = pgTable("sso_providers", {
+ id: serial("id").primaryKey(),
+ name: varchar("name", { length: 255 }).notNull(),
+ type: text("type").notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ displayOrder: integer("display_order").notNull().default(0),
+ config: text("config").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const sessions = pgTable("sessions", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ jwtToken: text("jwt_token").notNull(),
+ deviceType: text("device_type").notNull(),
+ deviceInfo: text("device_info").notNull(),
+ oidcSub: text("oidc_sub"),
+ oidcSid: text("oidc_sid"),
+ ssoProviderId: integer("sso_provider_id"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastActiveAt: text("last_active_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const trustedDevices = pgTable("trusted_devices", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ deviceFingerprint: text("device_fingerprint").notNull(),
+ deviceType: text("device_type").notNull(),
+ deviceInfo: text("device_info").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsedAt: text("last_used_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const webauthnCredentials = pgTable("webauthn_credentials", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ credentialId: text("credential_id").notNull(),
+ publicKey: text("public_key").notNull(),
+ counter: integer("counter").notNull().default(0),
+ deviceType: text("device_type"),
+ backedUp: boolean("backed_up").notNull().default(false),
+ transports: text("transports"),
+ userVerification: text("user_verification").notNull().default("preferred"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ lastUsedAt: text("last_used_at"),
+});
+
+export const hosts = pgTable("ssh_data", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ connectionType: text("connection_type").notNull().default("ssh"),
+ name: varchar("name", { length: 255 }),
+ ip: text("ip").notNull(),
+ port: integer("port").notNull(),
+ username: text("username").notNull(),
+ folder: text("folder"),
+ tags: text("tags"),
+ pin: boolean("pin").notNull().default(false),
+ authType: text("auth_type").notNull(),
+ useWarpgate: boolean("use_warpgate").notNull().default(false),
+ shareSshAuth: boolean("share_ssh_auth")
+ .notNull()
+ .default(false),
+ forceKeyboardInteractive: text("force_keyboard_interactive"),
+
+ password: text("password"),
+ key: text("key"),
+ keyPassword: text("key_password"),
+ keyType: text("key_type"),
+ sudoPassword: text("sudo_password"),
+
+ autostartPassword: text("autostart_password"),
+ autostartKey: text("autostart_key"),
+ autostartKeyPassword: text("autostart_key_password"),
+
+ credentialId: integer("credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ overrideCredentialUsername: boolean("override_credential_username"),
+ // When authType is "vault", the host authenticates via a Vault SSH signer
+ // profile (shared settings, no secrets). The signing certificate is obtained
+ // per-user at connect time via an interactive Vault OIDC flow.
+ vaultProfileId: integer("vault_profile_id").references(
+ () => vaultProfiles.id,
+ { onDelete: "set null" },
+ ),
+ enableTerminal: boolean("enable_terminal")
+ .notNull()
+ .default(true),
+ enableSessionLogging: boolean("enable_session_logging")
+ .notNull()
+ .default(true),
+ allowSessionSharing: boolean("allow_session_sharing")
+ .notNull()
+ .default(true),
+ enableCommandHistory: boolean("enable_command_history")
+ .notNull()
+ .default(true),
+ enableTunnel: boolean("enable_tunnel")
+ .notNull()
+ .default(true),
+ tunnelConnections: text("tunnel_connections"),
+ jumpHosts: text("jump_hosts"),
+ enableFileManager: boolean("enable_file_manager")
+ .notNull()
+ .default(true),
+ scpLegacy: boolean("scp_legacy").notNull().default(false),
+ enableDocker: boolean("enable_docker")
+ .notNull()
+ .default(false),
+ enableTmuxMonitor: boolean("enable_tmux_monitor")
+ .notNull()
+ .default(false),
+ showTerminalInSidebar: boolean("show_terminal_in_sidebar")
+ .notNull()
+ .default(true),
+ showFileManagerInSidebar: boolean("show_file_manager_in_sidebar")
+ .notNull()
+ .default(false),
+ showTunnelInSidebar: boolean("show_tunnel_in_sidebar")
+ .notNull()
+ .default(false),
+ showDockerInSidebar: boolean("show_docker_in_sidebar")
+ .notNull()
+ .default(false),
+ showServerStatsInSidebar: boolean("show_server_stats_in_sidebar")
+ .notNull()
+ .default(false),
+ defaultPath: text("default_path"),
+ statsConfig: text("stats_config"),
+ dockerConfig: text("docker_config"),
+ enableProxmox: boolean("enable_proxmox")
+ .notNull()
+ .default(false),
+ proxmoxConfig: text("proxmox_config"),
+ terminalConfig: text("terminal_config"),
+ quickActions: text("quick_actions"),
+ notes: text("notes"),
+ enableSsh: boolean("enable_ssh").notNull().default(true),
+ enableRdp: boolean("enable_rdp").notNull().default(false),
+ enableVnc: boolean("enable_vnc").notNull().default(false),
+ enableTelnet: boolean("enable_telnet").notNull().default(false),
+
+ sshPort: integer("ssh_port").default(22),
+ rdpPort: integer("rdp_port").default(3389),
+ vncPort: integer("vnc_port").default(5900),
+ telnetPort: integer("telnet_port").default(23),
+
+ rdpCredentialId: integer("rdp_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ rdpUser: text("rdp_user"),
+ rdpPassword: text("rdp_password"),
+ rdpDomain: text("rdp_domain"),
+ rdpSecurity: text("rdp_security"),
+ rdpIgnoreCert: boolean("rdp_ignore_cert").default(false),
+
+ vncCredentialId: integer("vnc_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+ vncPassword: text("vnc_password"),
+ vncUser: text("vnc_user"),
+
+ telnetUser: text("telnet_user"),
+ telnetPassword: text("telnet_password"),
+ telnetCredentialId: integer("telnet_credential_id").references(() => sshCredentials.id, { onDelete: "set null" }),
+
+ rdpAuthType: text("rdp_auth_type"),
+ vncAuthType: text("vnc_auth_type"),
+ telnetAuthType: text("telnet_auth_type"),
+
+ domain: text("domain"),
+ security: text("security"),
+ ignoreCert: boolean("ignore_cert").default(false),
+ guacamoleConfig: text("guacamole_config"),
+
+ useSocks5: boolean("use_socks5"),
+ socks5Host: text("socks5_host"),
+ socks5Port: integer("socks5_port"),
+ socks5Username: text("socks5_username"),
+ socks5Password: text("socks5_password"),
+ socks5ProxyChain: text("socks5_proxy_chain"),
+
+ // null = use the desktop app's global default; "local" | "remote" pins
+ // this specific host's SSH/Docker-console/Serial connections to originate
+ // from the embedded local backend or a connected remote sync server.
+ // Ignored for rdp/vnc/telnet, which always require the remote server.
+ connectionOrigin: text("connection_origin"),
+
+ macAddress: text("mac_address"),
+ wolBroadcastAddress: text("wol_broadcast_address"),
+ portKnockSequence: text("port_knock_sequence"),
+
+ hostKeyFingerprint: text("host_key_fingerprint"),
+ hostKeyType: text("host_key_type"),
+ hostKeyAlgorithm: text("host_key_algorithm").default("sha256"),
+ hostKeyFirstSeen: text("host_key_first_seen"),
+ hostKeyLastVerified: text("host_key_last_verified"),
+ hostKeyChangedCount: integer("host_key_changed_count").default(0),
+
+ // Stable identity used to match this row across two independently-seeded
+ // databases (the embedded backend and a connected remote server) during
+ // sync -- local autoincrement ids collide across instances.
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const fileManagerRecent = pgTable("file_manager_recent", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ lastOpened: text("last_opened")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const fileManagerPinned = pgTable("file_manager_pinned", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ pinnedAt: text("pinned_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const fileManagerShortcuts = pgTable("file_manager_shortcuts", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ path: text("path").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const transferRecent = pgTable("transfer_recent", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ sourceHostId: integer("source_host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ destHostId: integer("dest_host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ destPath: text("dest_path").notNull(),
+ destPathLabel: text("dest_path_label").notNull(),
+ lastUsed: text("last_used")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const dismissedAlerts = pgTable("dismissed_alerts", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ alertId: text("alert_id").notNull(),
+ dismissedAt: text("dismissed_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const sshCredentials = pgTable("ssh_credentials", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ tags: text("tags"),
+ authType: text("auth_type").notNull(),
+ username: text("username"),
+ password: text("password"),
+ key: text("key"),
+ privateKey: text("private_key"),
+ publicKey: text("public_key"),
+ keyPassword: text("key_password"),
+ keyType: text("key_type"),
+ detectedKeyType: text("detected_key_type"),
+
+ certPublicKey: text("cert_public_key"),
+
+
+ usageCount: integer("usage_count").notNull().default(0),
+ lastUsed: text("last_used"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const sshCredentialUsage = pgTable("ssh_credential_usage", {
+ id: serial("id").primaryKey(),
+ credentialId: integer("credential_id")
+ .notNull()
+ .references(() => sshCredentials.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ usedAt: text("used_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const snippets = pgTable("snippets", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ content: text("content").notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ order: integer("order").notNull().default(0),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ hostFilter: text("host_filter"),
+});
+
+export const snippetFolders = pgTable("snippet_folders", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ color: text("color"),
+ icon: text("icon"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const c2sTunnelPresets = pgTable("c2s_tunnel_presets", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ config: text("config").notNull(),
+ platform: text("platform"),
+ computerName: text("computer_name"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const snippetAccess = pgTable("snippet_access", {
+ id: serial("id").primaryKey(),
+ snippetId: integer("snippet_id")
+ .notNull()
+ .references(() => snippets.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "cascade" }),
+ roleId: integer("role_id").references(() => roles.id, {
+ onDelete: "cascade",
+ }),
+
+ grantedBy: varchar("granted_by", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ permissionLevel: text("permission_level").notNull().default("view"),
+
+ expiresAt: text("expires_at"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const sshFolders = pgTable("ssh_folders", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ color: text("color"),
+ icon: text("icon"),
+ credentialId: integer("credential_id").references(() => sshCredentials.id, {
+ onDelete: "set null",
+ }),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const recentActivity = pgTable("recent_activity", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ type: text("type").notNull(),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ hostName: text("host_name"),
+ timestamp: text("timestamp")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const commandHistory = pgTable("command_history", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ command: text("command").notNull(),
+ executedAt: text("executed_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const networkTopology = pgTable("network_topology", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ topology: text("topology"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const hostAccess = pgTable("host_access", {
+ id: serial("id").primaryKey(),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 })
+ .references(() => users.id, { onDelete: "cascade" }),
+ roleId: integer("role_id")
+ .references(() => roles.id, { onDelete: "cascade" }),
+
+ grantedBy: varchar("granted_by", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ permissionLevel: text("permission_level")
+ .notNull()
+ .default("connect"),
+
+ expiresAt: text("expires_at"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ lastAccessedAt: text("last_accessed_at"),
+ accessCount: integer("access_count").notNull().default(0),
+});
+
+export const sharedHostAuthOverrides = pgTable(
+ "shared_host_auth_overrides",
+ {
+ id: serial("id").primaryKey(),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ credentialId: integer("credential_id")
+ .notNull()
+ .references(() => sshCredentials.id, { onDelete: "cascade" }),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ (table) => [
+ uniqueIndex("shared_host_auth_overrides_host_user_protocol_unique").on(
+ table.hostId,
+ table.userId,
+ table.protocol,
+ ),
+ ],
+);
+
+export const sharedHostSecrets = pgTable(
+ "shared_host_secrets",
+ {
+ id: serial("id").primaryKey(),
+
+ hostAccessId: integer("host_access_id")
+ .notNull()
+ .references(() => hostAccess.id, { onDelete: "cascade" }),
+
+ targetUserId: varchar("target_user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ sourceType: text("source_type").notNull().default("credential"),
+
+ originalCredentialId: integer("original_credential_id").references(
+ () => sshCredentials.id,
+ { onDelete: "cascade" },
+ ),
+
+ encryptedUsername: text("encrypted_username"),
+ encryptedAuthType: text("encrypted_auth_type"),
+ encryptedPassword: text("encrypted_password"),
+ encryptedKey: text("encrypted_key"),
+ encryptedKeyPassword: text("encrypted_key_password"),
+ encryptedKeyType: text("encrypted_key_type"),
+ encryptedDomain: text("encrypted_domain"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_shared_host_secrets_scope").on(
+ table.hostAccessId,
+ table.targetUserId,
+ table.protocol,
+ ),
+ ],
+);
+
+export const roles = pgTable("roles", {
+ id: serial("id").primaryKey(),
+ name: varchar("name", { length: 255 }).notNull().unique(),
+ displayName: text("display_name").notNull(),
+ description: text("description"),
+
+ isSystem: boolean("is_system")
+ .notNull()
+ .default(false),
+
+ permissions: text("permissions"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const userRoles = pgTable(
+ "user_roles",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ roleId: integer("role_id")
+ .notNull()
+ .references(() => roles.id, { onDelete: "cascade" }),
+
+ grantedBy: varchar("granted_by", { length: 255 }).references(() => users.id, {
+ onDelete: "set null",
+ }),
+ grantedAt: text("granted_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_user_roles_user_role").on(table.userId, table.roleId)],
+);
+
+export const auditLogs = pgTable("audit_logs", {
+ id: serial("id").primaryKey(),
+
+ // Nullable on purpose: the trail outlives the account, and username keeps the
+ // entry attributable once the reference is gone.
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }),
+ username: text("username").notNull(),
+
+ action: text("action").notNull(),
+ resourceType: text("resource_type").notNull(),
+ resourceId: text("resource_id"),
+ resourceName: text("resource_name"),
+
+ details: text("details"),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
+
+ success: boolean("success").notNull(),
+ errorMessage: text("error_message"),
+
+ timestamp: text("timestamp")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const sessionRecordings = pgTable("session_recordings", {
+ id: serial("id").primaryKey(),
+
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // Nullable on purpose: a recording is evidence about the host as much as the
+ // person, so it outlives the account. username keeps it attributable.
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, { onDelete: "set null" }),
+ username: text("username"),
+ accessId: integer("access_id").references(() => hostAccess.id, {
+ onDelete: "set null",
+ }),
+
+ startedAt: text("started_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ endedAt: text("ended_at"),
+ duration: integer("duration"),
+
+ commands: text("commands"),
+ dangerousActions: text("dangerous_actions"),
+
+ recordingPath: text("recording_path"),
+ protocol: varchar("protocol", { length: 255 }).notNull().default("ssh"),
+ format: text("format").notNull().default("text"),
+
+ terminatedByOwner: boolean("terminated_by_owner")
+ .default(false),
+ terminationReason: text("termination_reason"),
+});
+
+export const sessionShares = pgTable("session_shares", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ ownerUserId: varchar("owner_user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ protocol: varchar("protocol", { length: 255 }).notNull(),
+
+ // Live-session binding: TerminalSessionManager's session.id for SSH, or
+ // guacd's own guacamoleConnectionId for rdp/vnc/telnet. Neither is a DB
+ // row (process-local, in-memory) so this intentionally has no FK.
+ sessionId: text("session_id").notNull(),
+ tabInstanceId: text("tab_instance_id"),
+
+ shareType: text("share_type").notNull(), // "link" | "user"
+ targetUserId: varchar("target_user_id", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ linkToken: varchar("link_token", { length: 255 }).unique(),
+
+ permissionLevel: text("permission_level").notNull().default("read-only"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ revokedAt: text("revoked_at"),
+
+ lastJoinedAt: text("last_joined_at"),
+ joinCount: integer("join_count").notNull().default(0),
+});
+
+export const sessionShareParticipants = pgTable(
+ "session_share_participants",
+ {
+ id: serial("id").primaryKey(),
+ shareId: varchar("share_id", { length: 255 })
+ .notNull()
+ .references(() => sessionShares.id, { onDelete: "cascade" }),
+
+ userId: varchar("user_id", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ guestLabel: text("guest_label"),
+
+ joinedAt: text("joined_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ leftAt: text("left_at"),
+ },
+);
+
+export const opksshTokens = pgTable(
+ "opkssh_tokens",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert").notNull(),
+ privateKey: text("private_key").notNull(),
+
+ email: text("email"),
+ sub: text("sub"),
+ issuer: text("issuer"),
+ audience: text("audience"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_opkssh_tokens_user_host").on(table.userId, table.hostId)],
+);
+
+// Vault SSH signer profiles. These hold ONLY non-secret connection settings and
+// are intended to be shared across users (shared === true makes a profile
+// visible to every user on the server). Each user authenticates to Vault via an
+// interactive OIDC flow at connect time; no tokens or keys are stored here.
+export const vaultProfiles = pgTable("vault_profiles", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ description: text("description"),
+ folder: text("folder"),
+ tags: text("tags"),
+ // Vault server connection (non-secret)
+ vaultAddr: text("vault_addr").notNull(),
+ vaultNamespace: text("vault_namespace"),
+ // OIDC auth method mount + role used to obtain a Vault token interactively
+ oidcMount: text("oidc_mount"),
+ oidcRole: text("oidc_role"),
+ // SSH secrets engine mount + signer role used to sign the ephemeral key
+ sshMount: text("ssh_mount"),
+ sshRole: text("ssh_role").notNull(),
+ validPrincipals: text("valid_principals"),
+ // Ephemeral keypair algorithm to generate per connection
+ keyType: text("key_type"),
+ // When true the profile is visible/usable by all users on the server
+ shared: boolean("shared").notNull().default(false),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+// Per-user cache of the ephemeral SSH private key + Vault-signed certificate.
+// Transient: rows live only until the certificate expires. Secret fields are
+// encrypted under the user's data-encryption key (see field-crypto.ts).
+export const vaultTokens = pgTable(
+ "vault_tokens",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ profileId: integer("profile_id")
+ .notNull()
+ .references(() => vaultProfiles.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert").notNull(),
+ privateKey: text("private_key").notNull(),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)],
+);
+
+export const apiKeys = pgTable("api_keys", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ tokenHash: text("token_hash").notNull(),
+ tokenPrefix: text("token_prefix").notNull(),
+ createdAt: text("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at"),
+ lastUsedAt: text("last_used_at"),
+ isActive: boolean("is_active").notNull().default(true),
+});
+
+export const userOpenTabs = pgTable("user_open_tabs", {
+ id: varchar("id", { length: 255 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ tabType: text("tab_type").notNull(),
+ hostId: integer("host_id").references(() => hosts.id, { onDelete: "cascade" }),
+ label: text("label").notNull(),
+ tabOrder: integer("tab_order").notNull().default(0),
+ backendSessionId: text("backend_session_id"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const userPreferences = pgTable("user_preferences", {
+ userId: varchar("user_id", { length: 255 })
+ .primaryKey()
+ .references(() => users.id, { onDelete: "cascade" }),
+ reopenTabsOnLogin: boolean("reopen_tabs_on_login")
+ .notNull()
+ .default(false),
+ theme: text("theme"),
+ fontSize: text("font_size"),
+ accentColor: text("accent_color"),
+ language: text("language"),
+ storageMode: text("storage_mode"),
+ commandAutocomplete: boolean("command_autocomplete"),
+ commandPaletteEnabled: boolean("command_palette_enabled"),
+ showHostTags: boolean("show_host_tags"),
+ hostTrayOnClick: boolean("host_tray_on_click"),
+ pinAppRail: boolean("pin_app_rail"),
+ expandAppRailOnHover: boolean("expand_app_rail_on_hover"),
+ foldersCollapsed: boolean("folders_collapsed"),
+ confirmSnippetExecution: boolean("confirm_snippet_execution"),
+ disableUpdateCheck: boolean("disable_update_check"),
+ confirmTabClose: boolean("confirm_tab_close"),
+ hiddenRailTabs: text("hidden_rail_tabs"),
+ compactHostView: boolean("compact_host_view"),
+ statusColorScheme: text("status_color_scheme"),
+ customThemes: text("custom_themes"),
+ customKeybindings: text("custom_keybindings"),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const hostMetricsPreferences = pgTable(
+ "host_metrics_preferences",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // JSON-encoded HostMetricsLayout. Layout has no secrets, so it is stored as
+ // plain JSON (no field-level encryption).
+ layout: text("layout").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // One layout per user per host. Enforced in production since the inline DDL
+ // creates it, but it was never declared here, so the generated Postgres and
+ // MySQL schemas lacked it — and the upsert has nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_host_metrics_prefs_user_host").on(table.userId, table.hostId),
+ ],
+);
+
+export const hostHealthChecks = pgTable(
+ "host_health_checks",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ // JSON array of { id, name, type: "tcp"|"http", target, port, path }
+ checks: text("checks").notNull(),
+ intervalSeconds: integer("interval_seconds").notNull().default(300),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // Same as above: one set of checks per user per host.
+ (table) => [
+ uniqueIndex("idx_host_health_checks_user_host").on(table.userId, table.hostId),
+ ],
+);
+
+export const hostHealthHistory = pgTable("host_health_history", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ checkId: text("check_id").notNull(),
+ ts: text("ts").notNull().default(sql`CURRENT_TIMESTAMP`),
+ ok: boolean("ok").notNull(),
+ latencyMs: integer("latency_ms"),
+ detail: text("detail"),
+});
+
+export const dashboardServiceLinks = pgTable("dashboard_service_links", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ label: text("label").notNull(),
+ url: text("url").notNull(),
+ order: integer("order").notNull().default(0),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+// --- termix-id begin ---
+// A user claims a unique public handle. Their published SSH public keys are
+// served at an unauthenticated resolver endpoint in authorized_keys format,
+// so any server can be provisioned with `curl /termix-id/u/ >> ~/.ssh/authorized_keys`.
+export const termixIdentities = pgTable("termix_identities", {
+ id: serial("id").primaryKey(),
+ // One Termix ID per user — enforced in schema, not just in code.
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .unique()
+ .references(() => users.id, { onDelete: "cascade" }),
+ handle: varchar("handle", { length: 255 }).notNull().unique(),
+ description: text("description"),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const termixIdentityKeys = pgTable("termix_identity_keys", {
+ id: serial("id").primaryKey(),
+ identityId: integer("identity_id")
+ .notNull()
+ .references(() => termixIdentities.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // Public keys are non-secret, so they are stored in plaintext (no field-level
+ // encryption). This is what lets the unauthenticated resolver serve them.
+ publicKey: text("public_key").notNull(),
+ // Raw algorithm token (e.g. "ssh-ed25519"), and a normalized group used for
+ // the / resolver filter (RSA / ED25519 / ECDSA / ...).
+ keyType: text("key_type").notNull(),
+ algorithm: text("algorithm").notNull(),
+ label: text("label"),
+ comment: text("comment"),
+ // "manual" (pasted) or "credential" (imported from an ssh_credentials entry).
+ source: text("source").notNull().default("manual"),
+ credentialId: integer("credential_id").references(() => sshCredentials.id, {
+ onDelete: "set null",
+ }),
+ enabled: boolean("enabled").notNull().default(true),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+// Per-identity certificate authority. Servers that trust this CA (via
+// TrustedUserCAKeys / @cert-authority) accept any user certificate it signs,
+// giving central revocation (rotate the CA) and expiry (cert validity).
+export const termixIdentityCa = pgTable("termix_identity_ca", {
+ id: serial("id").primaryKey(),
+ identityId: integer("identity_id")
+ .notNull()
+ .unique()
+ .references(() => termixIdentities.id, { onDelete: "cascade" }),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // CA public key (plaintext — it is published); CA private key is field-encrypted.
+ publicKey: text("public_key").notNull(),
+ privateKey: text("private_key").notNull(),
+ validityDays: integer("validity_days").notNull().default(90),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+// --- termix-id end ---
+
+// --- tmux-monitor begin ---
+export const tmuxSessionTags = pgTable("tmux_session_tags", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ sessionName: text("session_name").notNull(),
+ tag: text("tag").notNull(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+// --- tmux-monitor end ---
+
+// --- metrics-history begin ---
+export const hostMetricsHistory = pgTable("host_metrics_history", {
+ id: serial("id").primaryKey(),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ ts: text("ts")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ cpuPercent: doublePrecision("cpu_percent"),
+ memPercent: doublePrecision("mem_percent"),
+ diskPercent: doublePrecision("disk_percent"),
+ netRxBytes: integer("net_rx_bytes"),
+ netTxBytes: integer("net_tx_bytes"),
+});
+// --- metrics-history end ---
+
+// --- alerts begin ---
+export const alertRules = pgTable("alert_rules", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id").references(() => hosts.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ triggerType: text("trigger_type").notNull(),
+ thresholdValue: doublePrecision("threshold_value"),
+ thresholdDurationSeconds: integer("threshold_duration_seconds"),
+ cooldownMinutes: integer("cooldown_minutes").notNull().default(15),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const notificationChannels = pgTable("notification_channels", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ name: varchar("name", { length: 255 }).notNull(),
+ type: text("type").notNull(),
+ config: text("config").notNull(),
+ enabled: boolean("enabled").notNull().default(true),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const alertRuleChannels = pgTable("alert_rule_channels", {
+ id: serial("id").primaryKey(),
+ ruleId: integer("rule_id")
+ .notNull()
+ .references(() => alertRules.id, { onDelete: "cascade" }),
+ channelId: integer("channel_id")
+ .notNull()
+ .references(() => notificationChannels.id, { onDelete: "cascade" }),
+});
+
+export const alertFirings = pgTable("alert_firings", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ ruleId: integer("rule_id")
+ .notNull()
+ .references(() => alertRules.id, { onDelete: "cascade" }),
+ hostId: integer("host_id").notNull(),
+ hostName: text("host_name").notNull(),
+ firedAt: text("fired_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ resolvedAt: text("resolved_at"),
+ value: doublePrecision("value"),
+ message: text("message").notNull(),
+ severity: text("severity").notNull().default("warning"),
+ acknowledged: boolean("acknowledged").notNull().default(false),
+});
+// --- alerts end ---
+
+// --- homepage begin ---
+export const homepageItems = pgTable("homepage_items", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ typeId: text("type_id").notNull(),
+ title: text("title"),
+ config: text("config").notNull().default("{}"),
+ folderId: integer("folder_id"),
+ syncId: varchar("sync_id", { length: 255 }).unique(),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const homepageLayouts = pgTable("homepage_layouts", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .unique()
+ .references(() => users.id, { onDelete: "cascade" }),
+ // JSON: { entries: HomepageLayoutEntry[], pan: {x,y}, zoom: number }
+ layout: text("layout").notNull().default("{}"),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+// --- homepage end ---
+
+// --- sync begin ---
+// Records a delete for a synced entity type so the other side of a sync
+// pair (embedded desktop backend <-> connected remote server) learns about
+// the deletion instead of re-creating the row on its next pull.
+export const syncTombstones = pgTable("sync_tombstones", {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 255 })
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ entityType: text("entity_type").notNull(),
+ syncId: varchar("sync_id", { length: 255 }).notNull(),
+ deletedAt: text("deleted_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+});
+// --- sync end ---
diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts
index 423154b4..645f796a 100644
--- a/src/backend/database/db/schema.ts
+++ b/src/backend/database/db/schema.ts
@@ -1,4 +1,10 @@
-import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
+import {
+ sqliteTable,
+ text,
+ integer,
+ real,
+ uniqueIndex,
+} from "drizzle-orm/sqlite-core";
import { sql } from "drizzle-orm";
export const users = sqliteTable("users", {
@@ -124,6 +130,9 @@ export const hosts = sqliteTable("ssh_data", {
pin: integer("pin", { mode: "boolean" }).notNull().default(false),
authType: text("auth_type").notNull(),
useWarpgate: integer("use_warpgate", { mode: "boolean" }).notNull().default(false),
+ shareSshAuth: integer("share_ssh_auth", { mode: "boolean" })
+ .notNull()
+ .default(false),
forceKeyboardInteractive: text("force_keyboard_interactive"),
password: text("password"),
@@ -560,46 +569,85 @@ export const hostAccess = sqliteTable("host_access", {
.default(sql`CURRENT_TIMESTAMP`),
lastAccessedAt: text("last_accessed_at"),
accessCount: integer("access_count").notNull().default(0),
- overrideCredentialId: integer("override_credential_id").references(
- () => sshCredentials.id,
- { onDelete: "set null" },
- ),
});
-export const sharedHostSecrets = sqliteTable("shared_host_secrets", {
- id: integer("id").primaryKey({ autoIncrement: true }),
+export const sharedHostAuthOverrides = sqliteTable(
+ "shared_host_auth_overrides",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ protocol: text("protocol").notNull().default("ssh"),
+ credentialId: integer("credential_id")
+ .notNull()
+ .references(() => sshCredentials.id, { onDelete: "cascade" }),
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ (table) => [
+ uniqueIndex("shared_host_auth_overrides_host_user_protocol_unique").on(
+ table.hostId,
+ table.userId,
+ table.protocol,
+ ),
+ ],
+);
- hostAccessId: integer("host_access_id")
- .notNull()
- .references(() => hostAccess.id, { onDelete: "cascade" }),
+export const sharedHostSecrets = sqliteTable(
+ "shared_host_secrets",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
- targetUserId: text("target_user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
+ hostAccessId: integer("host_access_id")
+ .notNull()
+ .references(() => hostAccess.id, { onDelete: "cascade" }),
- protocol: text("protocol").notNull().default("ssh"),
- sourceType: text("source_type").notNull().default("credential"),
+ targetUserId: text("target_user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
- originalCredentialId: integer("original_credential_id").references(
- () => sshCredentials.id,
- { onDelete: "cascade" },
- ),
+ protocol: text("protocol").notNull().default("ssh"),
+ sourceType: text("source_type").notNull().default("credential"),
- encryptedUsername: text("encrypted_username"),
- encryptedAuthType: text("encrypted_auth_type"),
- encryptedPassword: text("encrypted_password"),
- encryptedKey: text("encrypted_key", { length: 16384 }),
- encryptedKeyPassword: text("encrypted_key_password"),
- encryptedKeyType: text("encrypted_key_type"),
- encryptedDomain: text("encrypted_domain"),
+ originalCredentialId: integer("original_credential_id").references(
+ () => sshCredentials.id,
+ { onDelete: "cascade" },
+ ),
- createdAt: text("created_at")
- .notNull()
- .default(sql`CURRENT_TIMESTAMP`),
- updatedAt: text("updated_at")
- .notNull()
- .default(sql`CURRENT_TIMESTAMP`),
-});
+ encryptedUsername: text("encrypted_username"),
+ encryptedAuthType: text("encrypted_auth_type"),
+ encryptedPassword: text("encrypted_password"),
+ encryptedKey: text("encrypted_key", { length: 16384 }),
+ encryptedKeyPassword: text("encrypted_key_password"),
+ encryptedKeyType: text("encrypted_key_type"),
+ encryptedDomain: text("encrypted_domain"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: text("updated_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_shared_host_secrets_scope").on(
+ table.hostAccessId,
+ table.targetUserId,
+ table.protocol,
+ ),
+ ],
+);
export const roles = sqliteTable("roles", {
id: integer("id").primaryKey({ autoIncrement: true }),
@@ -621,29 +669,36 @@ export const roles = sqliteTable("roles", {
.default(sql`CURRENT_TIMESTAMP`),
});
-export const userRoles = sqliteTable("user_roles", {
- id: integer("id").primaryKey({ autoIncrement: true }),
- userId: text("user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- roleId: integer("role_id")
- .notNull()
- .references(() => roles.id, { onDelete: "cascade" }),
-
- grantedBy: text("granted_by").references(() => users.id, {
- onDelete: "set null",
- }),
- grantedAt: text("granted_at")
- .notNull()
- .default(sql`CURRENT_TIMESTAMP`),
-});
+export const userRoles = sqliteTable(
+ "user_roles",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ roleId: integer("role_id")
+ .notNull()
+ .references(() => roles.id, { onDelete: "cascade" }),
+
+ grantedBy: text("granted_by").references(() => users.id, {
+ onDelete: "set null",
+ }),
+ grantedAt: text("granted_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_user_roles_user_role").on(table.userId, table.roleId)],
+);
export const auditLogs = sqliteTable("audit_logs", {
id: integer("id").primaryKey({ autoIncrement: true }),
- userId: text("user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
+ // Nullable on purpose: the trail outlives the account, and username keeps the
+ // entry attributable once the reference is gone.
+ userId: text("user_id").references(() => users.id, { onDelete: "set null" }),
username: text("username").notNull(),
action: text("action").notNull(),
@@ -669,9 +724,10 @@ export const sessionRecordings = sqliteTable("session_recordings", {
hostId: integer("host_id")
.notNull()
.references(() => hosts.id, { onDelete: "cascade" }),
- userId: text("user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
+ // Nullable on purpose: a recording is evidence about the host as much as the
+ // person, so it outlives the account. username keeps it attributable.
+ userId: text("user_id").references(() => users.id, { onDelete: "set null" }),
+ username: text("username"),
accessId: integer("access_id").references(() => hostAccess.id, {
onDelete: "set null",
}),
@@ -750,29 +806,36 @@ export const sessionShareParticipants = sqliteTable(
},
);
-export const opksshTokens = sqliteTable("opkssh_tokens", {
- id: integer("id").primaryKey({ autoIncrement: true }),
- userId: text("user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- hostId: integer("host_id")
- .notNull()
- .references(() => hosts.id, { onDelete: "cascade" }),
-
- sshCert: text("ssh_cert", { length: 8192 }).notNull(),
- privateKey: text("private_key", { length: 8192 }).notNull(),
-
- email: text("email"),
- sub: text("sub"),
- issuer: text("issuer"),
- audience: text("audience"),
-
- createdAt: text("created_at")
- .notNull()
- .default(sql`CURRENT_TIMESTAMP`),
- expiresAt: text("expires_at").notNull(),
- lastUsed: text("last_used"),
-});
+export const opksshTokens = sqliteTable(
+ "opkssh_tokens",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ hostId: integer("host_id")
+ .notNull()
+ .references(() => hosts.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert", { length: 8192 }).notNull(),
+ privateKey: text("private_key", { length: 8192 }).notNull(),
+
+ email: text("email"),
+ sub: text("sub"),
+ issuer: text("issuer"),
+ audience: text("audience"),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_opkssh_tokens_user_host").on(table.userId, table.hostId)],
+);
// Vault SSH signer profiles. These hold ONLY non-secret connection settings and
// are intended to be shared across users (shared === true makes a profile
@@ -813,24 +876,31 @@ export const vaultProfiles = sqliteTable("vault_profiles", {
// Per-user cache of the ephemeral SSH private key + Vault-signed certificate.
// Transient: rows live only until the certificate expires. Secret fields are
// encrypted under the user's data-encryption key (see field-crypto.ts).
-export const vaultTokens = sqliteTable("vault_tokens", {
- id: integer("id").primaryKey({ autoIncrement: true }),
- userId: text("user_id")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- profileId: integer("profile_id")
- .notNull()
- .references(() => vaultProfiles.id, { onDelete: "cascade" }),
-
- sshCert: text("ssh_cert", { length: 8192 }).notNull(),
- privateKey: text("private_key", { length: 8192 }).notNull(),
-
- createdAt: text("created_at")
- .notNull()
- .default(sql`CURRENT_TIMESTAMP`),
- expiresAt: text("expires_at").notNull(),
- lastUsed: text("last_used"),
-});
+export const vaultTokens = sqliteTable(
+ "vault_tokens",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ profileId: integer("profile_id")
+ .notNull()
+ .references(() => vaultProfiles.id, { onDelete: "cascade" }),
+
+ sshCert: text("ssh_cert", { length: 8192 }).notNull(),
+ privateKey: text("private_key", { length: 8192 }).notNull(),
+
+ createdAt: text("created_at")
+ .notNull()
+ .default(sql`CURRENT_TIMESTAMP`),
+ expiresAt: text("expires_at").notNull(),
+ lastUsed: text("last_used"),
+ },
+ // Declared inline in the production DDL as UNIQUE(...), but never here,
+ // so the generated Postgres and MySQL schemas allowed duplicates the
+ // SQLite deployment forbids — and the upsert had nothing to conflict on.
+ (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)],
+);
export const apiKeys = sqliteTable("api_keys", {
id: text("id").primaryKey(),
@@ -898,7 +968,9 @@ export const userPreferences = sqliteTable("user_preferences", {
.default(sql`CURRENT_TIMESTAMP`),
});
-export const hostMetricsPreferences = sqliteTable("host_metrics_preferences", {
+export const hostMetricsPreferences = sqliteTable(
+ "host_metrics_preferences",
+ {
id: integer("id").primaryKey({ autoIncrement: true }),
userId: text("user_id")
.notNull()
@@ -915,9 +987,18 @@ export const hostMetricsPreferences = sqliteTable("host_metrics_preferences", {
updatedAt: text("updated_at")
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
-});
+ },
+ // One layout per user per host. Enforced in production since the inline DDL
+ // creates it, but it was never declared here, so the generated Postgres and
+ // MySQL schemas lacked it — and the upsert has nothing to conflict on.
+ (table) => [
+ uniqueIndex("idx_host_metrics_prefs_user_host").on(table.userId, table.hostId),
+ ],
+);
-export const hostHealthChecks = sqliteTable("host_health_checks", {
+export const hostHealthChecks = sqliteTable(
+ "host_health_checks",
+ {
id: integer("id").primaryKey({ autoIncrement: true }),
userId: text("user_id")
.notNull()
@@ -934,7 +1015,12 @@ export const hostHealthChecks = sqliteTable("host_health_checks", {
updatedAt: text("updated_at")
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
-});
+ },
+ // Same as above: one set of checks per user per host.
+ (table) => [
+ uniqueIndex("idx_host_health_checks_user_host").on(table.userId, table.hostId),
+ ],
+);
export const hostHealthHistory = sqliteTable("host_health_history", {
id: integer("id").primaryKey({ autoIncrement: true }),
diff --git a/src/backend/database/repositories/alert-repository.ts b/src/backend/database/repositories/alert-repository.ts
index 9b55ca36..1c3edd31 100644
--- a/src/backend/database/repositories/alert-repository.ts
+++ b/src/backend/database/repositories/alert-repository.ts
@@ -1,4 +1,4 @@
-import { and, count, desc, eq, inArray, isNull, or } from "drizzle-orm";
+import { and, count, desc, eq, inArray, isNull, lt, or } from "drizzle-orm";
import {
alertFirings,
alertRuleChannels,
@@ -7,6 +7,9 @@ import {
notificationChannels,
} from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { sqlTimestampDaysAgo } from "./sql-timestamp.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
type AlertRuleRecord = typeof alertRules.$inferSelect;
type NotificationChannelRecord = typeof notificationChannels.$inferSelect;
@@ -117,16 +120,17 @@ export class AlertRepository {
config: string;
enabled: boolean;
}): Promise {
- const [created] = await this.context.drizzle
- .insert(notificationChannels)
- .values({
+ const [created] = await insertReturning(
+ this.context,
+ notificationChannels,
+ {
userId: input.userId,
name: input.name,
type: input.type,
config: input.config,
enabled: input.enabled,
- })
- .returning();
+ },
+ );
await this.afterWrite();
return mapChannelRow(created);
@@ -146,16 +150,15 @@ export class AlertRepository {
return this.findNotificationChannelForUser(id, userId);
}
- const [updated] = await this.context.drizzle
- .update(notificationChannels)
- .set(input)
- .where(
- and(
- eq(notificationChannels.id, id),
- eq(notificationChannels.userId, userId),
- ),
- )
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ notificationChannels,
+ input,
+ and(
+ eq(notificationChannels.id, id),
+ eq(notificationChannels.userId, userId),
+ ),
+ );
if (!updated) return null;
await this.afterWrite();
@@ -166,17 +169,16 @@ export class AlertRepository {
id: number,
userId: string,
): Promise {
- const deleted = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(notificationChannels)
.where(
and(
eq(notificationChannels.id, id),
eq(notificationChannels.userId, userId),
),
- )
- .returning({ id: notificationChannels.id });
+ );
- if (deleted.length === 0) return false;
+ if (rowsAffected(result) === 0) return false;
await this.afterWrite();
return true;
}
@@ -210,21 +212,18 @@ export class AlertRepository {
channels: number[];
now: string;
}): Promise {
- const [created] = await this.context.drizzle
- .insert(alertRules)
- .values({
- userId: input.userId,
- hostId: input.hostId,
- name: input.name,
- enabled: input.enabled,
- triggerType: input.triggerType,
- thresholdValue: input.thresholdValue,
- thresholdDurationSeconds: input.thresholdDurationSeconds,
- cooldownMinutes: input.cooldownMinutes,
- createdAt: input.now,
- updatedAt: input.now,
- })
- .returning();
+ const [created] = await insertReturning(this.context, alertRules, {
+ userId: input.userId,
+ hostId: input.hostId,
+ name: input.name,
+ enabled: input.enabled,
+ triggerType: input.triggerType,
+ thresholdValue: input.thresholdValue,
+ thresholdDurationSeconds: input.thresholdDurationSeconds,
+ cooldownMinutes: input.cooldownMinutes,
+ createdAt: input.now,
+ updatedAt: input.now,
+ });
const channels = await this.replaceRuleChannels(
created.id,
@@ -263,9 +262,10 @@ export class AlertRepository {
now: string;
},
): Promise {
- const [updated] = await this.context.drizzle
- .update(alertRules)
- .set({
+ const [updated] = await updateReturning(
+ this.context,
+ alertRules,
+ {
...(input.name !== undefined ? { name: input.name } : {}),
...(input.hostId !== undefined ? { hostId: input.hostId } : {}),
...(input.enabled !== undefined ? { enabled: input.enabled } : {}),
@@ -282,9 +282,9 @@ export class AlertRepository {
? { cooldownMinutes: input.cooldownMinutes }
: {}),
updatedAt: input.now,
- })
- .where(and(eq(alertRules.id, id), eq(alertRules.userId, userId)))
- .returning();
+ },
+ and(eq(alertRules.id, id), eq(alertRules.userId, userId)),
+ );
if (!updated) return null;
@@ -298,12 +298,11 @@ export class AlertRepository {
}
async deleteAlertRule(id: number, userId: string): Promise {
- const deleted = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(alertRules)
- .where(and(eq(alertRules.id, id), eq(alertRules.userId, userId)))
- .returning({ id: alertRules.id });
+ .where(and(eq(alertRules.id, id), eq(alertRules.userId, userId)));
- if (deleted.length === 0) return false;
+ if (rowsAffected(result) === 0) return false;
await this.afterWrite();
return true;
}
@@ -411,12 +410,15 @@ export class AlertRepository {
await this.afterWrite();
}
- pruneFiringsOlderThan(userId: string, days: number): void {
- this.context.sqlite
- ?.prepare(
- "DELETE FROM alert_firings WHERE user_id = ? AND fired_at < datetime('now', ?)",
- )
- .run(userId, `-${days} days`);
+ async pruneFiringsOlderThan(userId: string, days: number): Promise {
+ await this.context.drizzle
+ .delete(alertFirings)
+ .where(
+ and(
+ eq(alertFirings.userId, userId),
+ lt(alertFirings.firedAt, sqlTimestampDaysAgo(days)),
+ ),
+ );
}
async deleteByUserId(userId: string): Promise<{
@@ -438,10 +440,9 @@ export class AlertRepository {
.where(eq(notificationChannels.userId, userId))
).map((row) => row.id);
- const firingRows = await this.context.drizzle
+ const firingResult = await this.context.drizzle
.delete(alertFirings)
- .where(eq(alertFirings.userId, userId))
- .returning({ id: alertFirings.id });
+ .where(eq(alertFirings.userId, userId));
const linkFilters = [
...(ruleIds.length > 0
@@ -451,37 +452,34 @@ export class AlertRepository {
? [inArray(alertRuleChannels.channelId, channelIds)]
: []),
];
- const linkRows =
+ const linkResult =
linkFilters.length === 0
- ? []
+ ? null
: await this.context.drizzle
.delete(alertRuleChannels)
- .where(or(...linkFilters))
- .returning({ id: alertRuleChannels.id });
+ .where(or(...linkFilters));
- const ruleRows = await this.context.drizzle
+ const ruleResult = await this.context.drizzle
.delete(alertRules)
- .where(eq(alertRules.userId, userId))
- .returning({ id: alertRules.id });
- const channelRows = await this.context.drizzle
+ .where(eq(alertRules.userId, userId));
+ const result = await this.context.drizzle
.delete(notificationChannels)
- .where(eq(notificationChannels.userId, userId))
- .returning({ id: notificationChannels.id });
+ .where(eq(notificationChannels.userId, userId));
if (
- firingRows.length > 0 ||
- linkRows.length > 0 ||
- ruleRows.length > 0 ||
- channelRows.length > 0
+ rowsAffected(firingResult) > 0 ||
+ rowsAffected(linkResult) > 0 ||
+ rowsAffected(ruleResult) > 0 ||
+ rowsAffected(result) > 0
) {
await this.afterWrite();
}
return {
- firingsDeleted: firingRows.length,
- ruleLinksDeleted: linkRows.length,
- rulesDeleted: ruleRows.length,
- channelsDeleted: channelRows.length,
+ firingsDeleted: rowsAffected(firingResult),
+ ruleLinksDeleted: rowsAffected(linkResult),
+ rulesDeleted: rowsAffected(ruleResult),
+ channelsDeleted: rowsAffected(result),
};
}
diff --git a/src/backend/database/repositories/api-key-repository.ts b/src/backend/database/repositories/api-key-repository.ts
index a24b880a..c037f780 100644
--- a/src/backend/database/repositories/api-key-repository.ts
+++ b/src/backend/database/repositories/api-key-repository.ts
@@ -1,6 +1,8 @@
import { eq, and } from "drizzle-orm";
import { apiKeys, users } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { deleteReturning, insertReturning } from "./returning.js";
export type ApiKeyRecord = typeof apiKeys.$inferSelect;
export type NewApiKeyRecord = typeof apiKeys.$inferInsert;
@@ -24,10 +26,7 @@ export class ApiKeyRepository {
) {}
async create(apiKey: NewApiKeyRecord): Promise {
- const rows = await this.context.drizzle
- .insert(apiKeys)
- .values(apiKey)
- .returning();
+ const rows = await insertReturning(this.context, apiKeys, apiKey);
await this.afterWrite();
return rows[0];
}
@@ -78,23 +77,23 @@ export class ApiKeyRepository {
}
async delete(id: string): Promise {
- const rows = await this.context.drizzle
- .delete(apiKeys)
- .where(eq(apiKeys.id, id))
- .returning();
+ const rows = await deleteReturning(
+ this.context,
+ apiKeys,
+ eq(apiKeys.id, id),
+ );
await this.afterWrite();
return rows[0] ?? null;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(apiKeys)
- .where(eq(apiKeys.userId, userId))
- .returning({ id: apiKeys.id });
+ .where(eq(apiKeys.userId, userId));
await this.afterWrite();
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/audit-log-repository.ts b/src/backend/database/repositories/audit-log-repository.ts
index 6e9c86a9..6cae51e4 100644
--- a/src/backend/database/repositories/audit-log-repository.ts
+++ b/src/backend/database/repositories/audit-log-repository.ts
@@ -1,6 +1,9 @@
-import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
+import { and, asc, desc, eq, gte, inArray, lt, lte, sql } from "drizzle-orm";
import { auditLogs } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { sqlTimestampDaysAgo } from "./sql-timestamp.js";
+import { databaseLogger } from "../../utils/logger.js";
+import { countValue, rowsAffected } from "./mutation-result.js";
export type AuditLogRecord = typeof auditLogs.$inferSelect;
export type NewAuditLogRecord = typeof auditLogs.$inferInsert;
@@ -19,8 +22,31 @@ export type AuditLogPage = {
total: number;
};
-const PRUNE_MAX = 10000;
-const PRUNE_TARGET = 9000;
+export const AUDIT_RETENTION_DAYS_ENV = "AUDIT_LOG_RETENTION_DAYS";
+export const AUDIT_MAX_ENTRIES_ENV = "AUDIT_LOG_MAX_ENTRIES";
+
+const DEFAULT_MAX_ENTRIES = 10000;
+const PRUNE_TARGET_RATIO = 0.9;
+
+function positiveIntEnv(key: string, env: NodeJS.ProcessEnv): number | null {
+ const raw = Number(env[key]);
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : null;
+}
+
+/**
+ * How long entries are kept. Unset means "no time limit", in which case only
+ * the row cap applies.
+ */
+export function auditRetentionDays(
+ env: NodeJS.ProcessEnv = process.env,
+): number | null {
+ return positiveIntEnv(AUDIT_RETENTION_DAYS_ENV, env);
+}
+
+/** Hard ceiling on stored entries, so a busy install cannot fill the disk. */
+export function auditMaxEntries(env: NodeJS.ProcessEnv = process.env): number {
+ return positiveIntEnv(AUDIT_MAX_ENTRIES_ENV, env) ?? DEFAULT_MAX_ENTRIES;
+}
export class AuditLogRepository {
constructor(
@@ -57,10 +83,31 @@ export class AuditLogRepository {
return {
logs,
- total: totalResult[0]?.count ?? 0,
+ total: countValue(totalResult[0]?.count),
};
}
+ /**
+ * Reads matching entries in ascending time order for export.
+ *
+ * Paged rather than fetched whole so an export cannot pull an unbounded
+ * result set into memory, and ascending so a resumed or appended export
+ * continues where the previous one stopped.
+ */
+ async listForExport(input: {
+ filters: AuditLogFilters;
+ limit: number;
+ offset: number;
+ }): Promise {
+ return this.context.drizzle
+ .select()
+ .from(auditLogs)
+ .where(this.buildWhere(input.filters))
+ .orderBy(asc(auditLogs.timestamp), asc(auditLogs.id))
+ .limit(input.limit)
+ .offset(input.offset);
+ }
+
async listDistinctActions(): Promise {
const rows = await this.context.drizzle
.selectDistinct({ action: auditLogs.action })
@@ -70,17 +117,38 @@ export class AuditLogRepository {
return rows.map((row) => row.action);
}
- async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
- .delete(auditLogs)
- .where(eq(auditLogs.userId, userId))
- .returning({ id: auditLogs.id });
+ /**
+ * Detaches entries from a user being deleted instead of removing them.
+ *
+ * The schema already relaxed this foreign key to ON DELETE SET NULL, but the
+ * account-deletion path deletes the rows explicitly, which undoes that. An
+ * audit trail that vanishes with the account it recorded cannot answer the
+ * question it exists for, and offboarding is exactly when that question gets
+ * asked. `username` is denormalised, so the entry stays attributable.
+ */
+ async anonymizeByUserId(userId: string): Promise {
+ const result = await this.context.drizzle
+ .update(auditLogs)
+ .set({ userId: null })
+ .where(eq(auditLogs.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
+ }
+
+ async deleteByUserId(userId: string): Promise {
+ const result = await this.context.drizzle
+ .delete(auditLogs)
+ .where(eq(auditLogs.userId, userId));
+
+ if (rowsAffected(result) > 0) {
+ await this.afterWrite();
+ }
+
+ return rowsAffected(result);
}
private buildWhere(filters: AuditLogFilters) {
@@ -105,28 +173,73 @@ export class AuditLogRepository {
}
private async pruneIfNeeded(): Promise {
+ await this.pruneExpired();
+ await this.pruneOverflow();
+ }
+
+ /** Drops entries past the configured retention window. */
+ private async pruneExpired(): Promise {
+ const days = auditRetentionDays();
+ if (days === null) return;
+
+ const cutoff = sqlTimestampDaysAgo(days);
+ const result = await this.context.drizzle
+ .delete(auditLogs)
+ .where(lt(auditLogs.timestamp, cutoff));
+
+ if (rowsAffected(result) > 0) {
+ databaseLogger.info(
+ `Pruned ${rowsAffected(result)} audit entries past retention`,
+ {
+ operation: "audit_retention_prune",
+ removed: rowsAffected(result),
+ retentionDays: days,
+ cutoff,
+ },
+ );
+ }
+ }
+
+ /**
+ * Enforces the row cap. Unlike retention this discards entries that are still
+ * within the window, so it is reported as a warning: it means the ceiling is
+ * too low for how much this install audits, and evidence is being lost.
+ */
+ private async pruneOverflow(): Promise {
+ const max = auditMaxEntries();
const countResult = await this.context.drizzle
.select({ count: sql`COUNT(*)` })
.from(auditLogs);
- const count = countResult[0]?.count ?? 0;
+ const count = countValue(countResult[0]?.count);
- if (count < PRUNE_MAX) {
- return;
- }
+ if (count < max) return;
- const deleteCount = count - PRUNE_TARGET;
+ const deleteCount = count - Math.floor(max * PRUNE_TARGET_RATIO);
const rows = await this.context.drizzle
- .select({ id: auditLogs.id })
+ .select({ id: auditLogs.id, timestamp: auditLogs.timestamp })
.from(auditLogs)
.orderBy(asc(auditLogs.timestamp))
.limit(deleteCount);
- const ids = rows.map((row) => row.id);
+ if (rows.length === 0) return;
- if (ids.length > 0) {
- await this.context.drizzle
- .delete(auditLogs)
- .where(inArray(auditLogs.id, ids));
- }
+ await this.context.drizzle.delete(auditLogs).where(
+ inArray(
+ auditLogs.id,
+ rows.map((row) => row.id),
+ ),
+ );
+
+ databaseLogger.warn(
+ `Audit log hit its ${max}-entry cap; discarded ${rows.length} entries`,
+ {
+ operation: "audit_overflow_prune",
+ removed: rows.length,
+ maxEntries: max,
+ oldestRemoved: rows[0]?.timestamp,
+ newestRemoved: rows[rows.length - 1]?.timestamp,
+ hint: `Raise ${AUDIT_MAX_ENTRIES_ENV}, or set ${AUDIT_RETENTION_DAYS_ENV} and export older entries before they are dropped.`,
+ },
+ );
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/c2s-tunnel-preset-repository.ts b/src/backend/database/repositories/c2s-tunnel-preset-repository.ts
index b0417132..0c14689a 100644
--- a/src/backend/database/repositories/c2s-tunnel-preset-repository.ts
+++ b/src/backend/database/repositories/c2s-tunnel-preset-repository.ts
@@ -1,6 +1,8 @@
import { and, asc, eq, sql } from "drizzle-orm";
import { c2sTunnelPresets } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type C2sTunnelPresetRecord = typeof c2sTunnelPresets.$inferSelect;
@@ -64,16 +66,13 @@ export class C2sTunnelPresetRepository {
userId: string,
input: C2sTunnelPresetCreateInput,
): Promise {
- const [created] = await this.context.drizzle
- .insert(c2sTunnelPresets)
- .values({
- userId,
- name: input.name,
- config: input.config,
- platform: input.platform ?? null,
- computerName: input.computerName ?? null,
- })
- .returning();
+ const [created] = await insertReturning(this.context, c2sTunnelPresets, {
+ userId,
+ name: input.name,
+ config: input.config,
+ platform: input.platform ?? null,
+ computerName: input.computerName ?? null,
+ });
await this.afterWrite();
return created;
@@ -84,16 +83,15 @@ export class C2sTunnelPresetRepository {
id: number,
updates: C2sTunnelPresetUpdateInput,
): Promise {
- const [updated] = await this.context.drizzle
- .update(c2sTunnelPresets)
- .set({
+ const [updated] = await updateReturning(
+ this.context,
+ c2sTunnelPresets,
+ {
...updates,
updatedAt: sql`CURRENT_TIMESTAMP`,
- })
- .where(
- and(eq(c2sTunnelPresets.id, id), eq(c2sTunnelPresets.userId, userId)),
- )
- .returning();
+ },
+ and(eq(c2sTunnelPresets.id, id), eq(c2sTunnelPresets.userId, userId)),
+ );
if (updated) {
await this.afterWrite();
@@ -103,31 +101,29 @@ export class C2sTunnelPresetRepository {
}
async deleteForUser(userId: string, id: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(c2sTunnelPresets)
.where(
and(eq(c2sTunnelPresets.id, id), eq(c2sTunnelPresets.userId, userId)),
- )
- .returning({ id: c2sTunnelPresets.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(c2sTunnelPresets)
- .where(eq(c2sTunnelPresets.userId, userId))
- .returning({ id: c2sTunnelPresets.id });
+ .where(eq(c2sTunnelPresets.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/command-history-repository.ts b/src/backend/database/repositories/command-history-repository.ts
index 5bf72b3a..da7b6571 100644
--- a/src/backend/database/repositories/command-history-repository.ts
+++ b/src/backend/database/repositories/command-history-repository.ts
@@ -1,6 +1,8 @@
import { and, desc, eq, inArray, sql } from "drizzle-orm";
import { commandHistory } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type CommandHistoryRecord = typeof commandHistory.$inferSelect;
@@ -16,10 +18,12 @@ export class CommandHistoryRepository {
command: string,
executedAt = new Date().toISOString(),
): Promise {
- const [created] = await this.context.drizzle
- .insert(commandHistory)
- .values({ userId, hostId, command, executedAt })
- .returning();
+ const [created] = await insertReturning(this.context, commandHistory, {
+ userId,
+ hostId,
+ command,
+ executedAt,
+ });
await this.afterWrite();
return created;
}
@@ -76,7 +80,7 @@ export class CommandHistoryRepository {
hostId: number,
command: string,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(commandHistory)
.where(
and(
@@ -84,45 +88,42 @@ export class CommandHistoryRepository {
eq(commandHistory.hostId, hostId),
eq(commandHistory.command, command),
),
- )
- .returning({ id: commandHistory.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByUserAndHost(userId: string, hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(commandHistory)
.where(
and(
eq(commandHistory.userId, userId),
eq(commandHistory.hostId, hostId),
),
- )
- .returning({ id: commandHistory.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(commandHistory)
- .where(eq(commandHistory.hostId, hostId))
- .returning({ id: commandHistory.id });
+ .where(eq(commandHistory.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostIds(hostIds: number[]): Promise {
@@ -130,29 +131,27 @@ export class CommandHistoryRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(commandHistory)
- .where(inArray(commandHistory.hostId, hostIds))
- .returning({ id: commandHistory.id });
+ .where(inArray(commandHistory.hostId, hostIds));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(commandHistory)
- .where(eq(commandHistory.userId, userId))
- .returning({ id: commandHistory.id });
+ .where(eq(commandHistory.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/credential-repository.ts b/src/backend/database/repositories/credential-repository.ts
index 5ec291be..dd6b12d1 100644
--- a/src/backend/database/repositories/credential-repository.ts
+++ b/src/backend/database/repositories/credential-repository.ts
@@ -3,6 +3,12 @@ import { randomUUID } from "crypto";
import { sshCredentials, sshCredentialUsage } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type CredentialRecord = typeof sshCredentials.$inferSelect;
export type NewCredentialRecord = typeof sshCredentials.$inferInsert;
@@ -17,10 +23,10 @@ export class CredentialRepository {
) {}
async create(credential: NewCredentialRecord): Promise {
- const rows = await this.context.drizzle
- .insert(sshCredentials)
- .values({ syncId: randomUUID(), ...credential })
- .returning();
+ const rows = await insertReturning(this.context, sshCredentials, {
+ syncId: randomUUID(),
+ ...credential,
+ });
await this.afterWrite();
return rows[0];
}
@@ -46,10 +52,11 @@ export class CredentialRepository {
delete (encryptedCredential as Partial).id;
}
- const rows = await this.context.drizzle
- .insert(sshCredentials)
- .values(encryptedCredential as NewCredentialRecord)
- .returning();
+ const rows = await insertReturning(
+ this.context,
+ sshCredentials,
+ encryptedCredential as NewCredentialRecord,
+ );
await this.afterWrite();
return DataCrypto.decryptRecord(
@@ -143,7 +150,7 @@ export class CredentialRepository {
oldName: string,
newName: string,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(sshCredentials)
.set({ folder: newName, updatedAt: sql`CURRENT_TIMESTAMP` })
.where(
@@ -151,14 +158,13 @@ export class CredentialRepository {
eq(sshCredentials.userId, userId),
eq(sshCredentials.folder, oldName),
),
- )
- .returning({ id: sshCredentials.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async updateForUser(
@@ -166,16 +172,15 @@ export class CredentialRepository {
credentialId: number,
update: CredentialUpdate,
): Promise {
- const rows = await this.context.drizzle
- .update(sshCredentials)
- .set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
- .where(
- and(
- eq(sshCredentials.id, credentialId),
- eq(sshCredentials.userId, userId),
- ),
- )
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ sshCredentials,
+ { ...update, updatedAt: sql`CURRENT_TIMESTAMP` },
+ and(
+ eq(sshCredentials.id, credentialId),
+ eq(sshCredentials.userId, userId),
+ ),
+ );
await this.afterWrite();
return rows[0] ?? null;
@@ -193,16 +198,15 @@ export class CredentialRepository {
userDataKey,
);
- const rows = await this.context.drizzle
- .update(sshCredentials)
- .set({ ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` })
- .where(
- and(
- eq(sshCredentials.id, credentialId),
- eq(sshCredentials.userId, userId),
- ),
- )
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ sshCredentials,
+ { ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` },
+ and(
+ eq(sshCredentials.id, credentialId),
+ eq(sshCredentials.userId, userId),
+ ),
+ );
await this.afterWrite();
return this.decryptOne(rows[0] ?? null, userId);
@@ -212,31 +216,29 @@ export class CredentialRepository {
userId: string,
credentialId: number,
): Promise<{ syncId: string | null } | null> {
- const rows = await this.context.drizzle
- .delete(sshCredentials)
- .where(
- and(
- eq(sshCredentials.id, credentialId),
- eq(sshCredentials.userId, userId),
- ),
- )
- .returning({ syncId: sshCredentials.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ sshCredentials,
+ and(
+ eq(sshCredentials.id, credentialId),
+ eq(sshCredentials.userId, userId),
+ ),
+ );
await this.afterWrite();
- return rows[0] ?? null;
+ return rows[0] ? { syncId: rows[0].syncId } : null;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sshCredentials)
- .where(eq(sshCredentials.userId, userId))
- .returning({ id: sshCredentials.id });
+ .where(eq(sshCredentials.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async recordUsage(
diff --git a/src/backend/database/repositories/dashboard-service-link-repository.ts b/src/backend/database/repositories/dashboard-service-link-repository.ts
index b06c2100..3aa6d253 100644
--- a/src/backend/database/repositories/dashboard-service-link-repository.ts
+++ b/src/backend/database/repositories/dashboard-service-link-repository.ts
@@ -2,6 +2,12 @@ import { and, asc, eq } from "drizzle-orm";
import { randomUUID } from "crypto";
import { dashboardServiceLinks } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type DashboardServiceLinkRecord =
typeof dashboardServiceLinks.$inferSelect;
@@ -38,9 +44,10 @@ export class DashboardServiceLinkRepository {
const nextOrder =
existing.length > 0 ? existing[existing.length - 1].order + 1 : 0;
- const [created] = await this.context.drizzle
- .insert(dashboardServiceLinks)
- .values({
+ const [created] = await insertReturning(
+ this.context,
+ dashboardServiceLinks,
+ {
syncId: randomUUID(),
userId,
label: input.label,
@@ -48,8 +55,8 @@ export class DashboardServiceLinkRepository {
order: nextOrder,
createdAt,
updatedAt: createdAt,
- })
- .returning();
+ },
+ );
await this.afterWrite();
return created;
}
@@ -77,16 +84,15 @@ export class DashboardServiceLinkRepository {
id: number,
updates: DashboardServiceLinkUpdate,
): Promise {
- const [updated] = await this.context.drizzle
- .update(dashboardServiceLinks)
- .set({ ...updates, updatedAt: new Date().toISOString() })
- .where(
- and(
- eq(dashboardServiceLinks.id, id),
- eq(dashboardServiceLinks.userId, userId),
- ),
- )
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ dashboardServiceLinks,
+ { ...updates, updatedAt: new Date().toISOString() },
+ and(
+ eq(dashboardServiceLinks.id, id),
+ eq(dashboardServiceLinks.userId, userId),
+ ),
+ );
if (updated) {
await this.afterWrite();
@@ -99,32 +105,30 @@ export class DashboardServiceLinkRepository {
userId: string,
id: number,
): Promise<{ syncId: string | null } | null> {
- const rows = await this.context.drizzle
- .delete(dashboardServiceLinks)
- .where(
- and(
- eq(dashboardServiceLinks.id, id),
- eq(dashboardServiceLinks.userId, userId),
- ),
- )
- .returning({ syncId: dashboardServiceLinks.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ dashboardServiceLinks,
+ and(
+ eq(dashboardServiceLinks.id, id),
+ eq(dashboardServiceLinks.userId, userId),
+ ),
+ );
if (rows.length === 0) return null;
await this.afterWrite();
- return rows[0];
+ return { syncId: rows[0].syncId };
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(dashboardServiceLinks)
- .where(eq(dashboardServiceLinks.userId, userId))
- .returning({ id: dashboardServiceLinks.id });
+ .where(eq(dashboardServiceLinks.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/database-context.ts b/src/backend/database/repositories/database-context.ts
index 7666cc41..4f0bb476 100644
--- a/src/backend/database/repositories/database-context.ts
+++ b/src/backend/database/repositories/database-context.ts
@@ -1,9 +1,41 @@
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
-import type { Database as BetterSqliteDatabase } from "better-sqlite3";
import type * as schema from "../db/schema.js";
+// Re-exported so repositories can keep importing it from here, but defined in
+// db/dialect.ts — a local copy that said "sqlite" survived here for a while and
+// typed every context as SQLite-only while the runtime already carried all
+// three, which silently made the dialect branches unreachable to the checker.
+export type { DatabaseDialect } from "../db/dialect.js";
+import type { DatabaseDialect } from "../db/dialect.js";
+
+/**
+ * The database handle repositories work against.
+ *
+ * Typed as the SQLite instance on purpose. drizzle's three Database classes
+ * share no base class and their signatures are incompatible: a union is not
+ * callable, and a generic would have to be threaded through all 43
+ * repositories and every method on them.
+ *
+ * This is a deliberate approximation, not an accident. The query-builder
+ * surface the repositories actually use is the same on all three engines, and
+ * that equivalence is asserted in multi-dialect.test.ts rather than assumed —
+ * identifier quoting, placeholder style and value coercion are all covered
+ * there. At runtime this may hold a Postgres or MySQL instance.
+ *
+ * The one place the surfaces genuinely differ is RETURNING, which MySQL lacks;
+ * see mutation-result.ts for how that is absorbed.
+ */
+export type PortableDatabase = BetterSQLite3Database;
+
+/**
+ * What a repository is allowed to touch.
+ *
+ * Deliberately drizzle-only: with no raw driver handle here, no repository can
+ * reach for engine-specific SQL. Retention queries that previously needed
+ * `datetime('now', ?)` compute their cutoff in JS instead — see
+ * ./sql-timestamp.ts.
+ */
export interface DatabaseContext {
- dialect: "sqlite";
- drizzle: BetterSQLite3Database;
- sqlite?: BetterSqliteDatabase;
+ dialect: DatabaseDialect;
+ drizzle: PortableDatabase;
}
diff --git a/src/backend/database/repositories/dismissed-alert-repository.ts b/src/backend/database/repositories/dismissed-alert-repository.ts
index e44d20b1..87cf40d8 100644
--- a/src/backend/database/repositories/dismissed-alert-repository.ts
+++ b/src/backend/database/repositories/dismissed-alert-repository.ts
@@ -1,6 +1,7 @@
import { and, eq } from "drizzle-orm";
import { dismissedAlerts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type DismissedAlertRecord = typeof dismissedAlerts.$inferSelect;
@@ -72,34 +73,32 @@ export class DismissedAlertRepository {
}
async deleteForUser(userId: string, alertId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(dismissedAlerts)
.where(
and(
eq(dismissedAlerts.userId, userId),
eq(dismissedAlerts.alertId, alertId),
),
- )
- .returning({ id: dismissedAlerts.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(dismissedAlerts)
- .where(eq(dismissedAlerts.userId, userId))
- .returning({ id: dismissedAlerts.id });
+ .where(eq(dismissedAlerts.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts
index 42dd08d8..30673432 100644
--- a/src/backend/database/repositories/factory.ts
+++ b/src/backend/database/repositories/factory.ts
@@ -1,5 +1,7 @@
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
import { getDb, getSqlite } from "../db/index.js";
+import { needsExplicitPersist, resolveDatabaseDialect } from "../db/dialect.js";
+import { primeSettingsCache, readCachedSetting } from "./settings-cache.js";
import type { DatabaseContext } from "./database-context.js";
import { WebauthnCredentialRepository } from "./webauthn-credential-repository.js";
import { AlertRepository } from "./alert-repository.js";
@@ -29,6 +31,7 @@ import { SessionRecordingRepository } from "./session-recording-repository.js";
import { SessionRepository } from "./session-repository.js";
import { SessionShareRepository } from "./session-share-repository.js";
import { SettingsRepository } from "./settings-repository.js";
+import { SharedHostAuthOverrideRepository } from "./shared-host-auth-override-repository.js";
import { SharedHostSecretsRepository } from "./shared-host-secrets-repository.js";
import { SnippetRepository } from "./snippet-repository.js";
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js";
@@ -45,25 +48,62 @@ import { UserRepository } from "./user-repository.js";
import { VaultProfileRepository } from "./vault-profile-repository.js";
import { VaultTokenRepository } from "./vault-token-repository.js";
+/**
+ * The context every repository runs against.
+ *
+ * The dialect has to be resolved, not assumed: it is what `returning.ts` reads
+ * to decide whether it can ask for RETURNING, and whether an upsert spells
+ * itself `onConflictDoUpdate` or `onDuplicateKeyUpdate`. Reporting "sqlite"
+ * while connected to MySQL makes the second of those a TypeError on the first
+ * write.
+ *
+ * Both cross-dialect harnesses build a DatabaseContext themselves, so neither
+ * exercises this function — see tests/database/repositories/factory-context.
+ */
export function createCurrentRepositoryContext(): DatabaseContext {
return {
- dialect: "sqlite",
+ dialect: resolveDatabaseDialect(),
drizzle: getDb(),
- sqlite: getSqlite(),
};
}
+/**
+ * Post-write hook handed to every repository.
+ *
+ * Only meaningful for SQLite, where the database lives in memory and has to be
+ * serialised back to its encrypted file. On Postgres and MySQL the write is
+ * already durable, so no hook is installed at all rather than one that does
+ * nothing — repositories call it as `this.onWrite?.()`.
+ */
export function createCurrentRepositoryWriteHook(
reason: string,
-): () => Promise {
+): (() => Promise) | undefined {
+ if (!needsExplicitPersist(resolveDatabaseDialect())) return undefined;
return () => DatabaseSaveTrigger.forceSave(reason);
}
+/**
+ * Raw driver handle for the few synchronous call sites that cannot await —
+ * getCurrentSettingValue below, and settings reads during startup. Repositories
+ * must not use this: they take a DatabaseContext, which is drizzle-only.
+ * Porting to another engine means giving these callers an async path first.
+ */
export function getCurrentRepositorySqlite() {
return getSqlite();
}
+/**
+ * Synchronous settings read.
+ *
+ * SQLite can be queried synchronously, so it is read directly and stays
+ * authoritative. Other engines have no synchronous query, so the value comes
+ * from the cache primed at startup and kept current by SettingsRepository.
+ */
export function getCurrentSettingValue(key: string): string | null {
+ if (!needsExplicitPersist(resolveDatabaseDialect())) {
+ return readCachedSetting(key);
+ }
+
const row = getCurrentRepositorySqlite()
.prepare("SELECT value FROM settings WHERE key = ?")
.get(key) as { value?: string } | undefined;
@@ -283,6 +323,15 @@ export function createCurrentSharedHostSecretsRepository(): SharedHostSecretsRep
);
}
+export function createCurrentSharedHostAuthOverrideRepository(): SharedHostAuthOverrideRepository {
+ return new SharedHostAuthOverrideRepository(
+ createCurrentRepositoryContext(),
+ createCurrentRepositoryWriteHook(
+ "shared_host_auth_override_repository_write",
+ ),
+ );
+}
+
export function createCurrentSnippetRepository(): SnippetRepository {
return new SnippetRepository(
createCurrentRepositoryContext(),
@@ -370,3 +419,69 @@ export function createCurrentVaultTokenRepository(): VaultTokenRepository {
createCurrentRepositoryWriteHook("vault_token_repository_write"),
);
}
+
+/**
+ * Loads the settings cache. Must run during startup on engines without a
+ * synchronous read, before anything calls getCurrentSettingValue.
+ */
+export async function primeCurrentSettingsCache(): Promise {
+ const rows = await createCurrentSettingsRepository().listAll();
+ primeSettingsCache(rows);
+}
+
+/**
+ * How often a replica re-reads the settings table.
+ *
+ * Override with SETTINGS_CACHE_REFRESH_SECONDS; 0 disables the refresh.
+ */
+const REFRESH_SECONDS_ENV = "SETTINGS_CACHE_REFRESH_SECONDS";
+const DEFAULT_REFRESH_SECONDS = 30;
+
+let refreshTimer: NodeJS.Timeout | null = null;
+
+/**
+ * Keeps the settings cache from drifting on a multi-replica deployment.
+ *
+ * The cache is per-process and updated in the process that writes. That is
+ * enough for SQLite, where there is only ever one process. On Postgres and
+ * MySQL — which exist here precisely so more than one instance can share the
+ * data — a setting changed on one replica would otherwise never reach the
+ * others, because the synchronous read has no way to go back to the database.
+ *
+ * Periodic re-priming does not make the value immediately consistent. It bounds
+ * how long it can be wrong, which is the difference between a setting that
+ * takes effect on the next tick and one that takes effect at the next restart.
+ */
+export function startSettingsCacheRefresh(
+ env = process.env,
+ refresh: () => Promise = primeCurrentSettingsCache,
+): void {
+ if (refreshTimer) return;
+
+ const seconds = refreshIntervalSeconds(env);
+ if (seconds === null) return;
+
+ refreshTimer = setInterval(() => {
+ void refresh().catch(() => {
+ // A failed refresh leaves the previous values in place, which is the
+ // right outcome: a transient database blip should not blank the cache.
+ // Every caller reads a missing setting as "use the default", so an empty
+ // cache would silently revert configuration across the deployment.
+ });
+ }, seconds * 1000);
+
+ refreshTimer.unref();
+}
+
+/** The configured interval, or null when refreshing is switched off. */
+export function refreshIntervalSeconds(env = process.env): number | null {
+ const seconds = Number(env[REFRESH_SECONDS_ENV] ?? DEFAULT_REFRESH_SECONDS);
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : null;
+}
+
+/** Test seam. */
+export function stopSettingsCacheRefresh(): void {
+ if (!refreshTimer) return;
+ clearInterval(refreshTimer);
+ refreshTimer = null;
+}
diff --git a/src/backend/database/repositories/field-encryption-boundary.ts b/src/backend/database/repositories/field-encryption-boundary.ts
deleted file mode 100644
index 04cdf02d..00000000
--- a/src/backend/database/repositories/field-encryption-boundary.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { FieldCrypto } from "../../utils/field-crypto.js";
-import { LazyFieldEncryption } from "../../utils/lazy-field-encryption.js";
-
-const FIELD_ENCRYPTION_POLICY = {
- users: {
- sensitive: new Set([
- "passwordHash",
- "clientSecret",
- "totpSecret",
- "totpBackupCodes",
- "oidcIdentifier",
- ]),
- plaintext: new Set(["id", "username", "isAdmin", "isOidc"]),
- },
- ssh_data: {
- sensitive: new Set([
- "password",
- "key",
- "keyPassword",
- "sudoPassword",
- "autostartPassword",
- "autostartKey",
- "autostartKeyPassword",
- "socks5Password",
- "rdpPassword",
- "vncPassword",
- "telnetPassword",
- ]),
- plaintext: new Set([
- "id",
- "userId",
- "connectionType",
- "name",
- "ip",
- "port",
- "username",
- "folder",
- "tags",
- "authType",
- "credentialId",
- ]),
- },
- ssh_credentials: {
- sensitive: new Set([
- "password",
- "key",
- "privateKey",
- "publicKey",
- "keyPassword",
- ]),
- plaintext: new Set([
- "id",
- "userId",
- "name",
- "description",
- "folder",
- "tags",
- "authType",
- "username",
- "keyType",
- "detectedKeyType",
- "usageCount",
- "lastUsed",
- ]),
- },
- opkssh_tokens: {
- sensitive: new Set(["sshCert", "privateKey"]),
- plaintext: new Set(["id", "userId", "hostId", "createdAt", "expiresAt"]),
- },
- termix_identity_ca: {
- sensitive: new Set(["privateKey"]),
- plaintext: new Set(["id", "publicKey", "createdAt", "updatedAt"]),
- },
- vault_tokens: {
- sensitive: new Set(["sshCert", "privateKey"]),
- plaintext: new Set(["id", "userId", "profileId", "expiresAt"]),
- },
-} as const;
-
-type PolicyTable = keyof typeof FIELD_ENCRYPTION_POLICY;
-export type FieldClassification = "sensitive" | "plaintext" | "unknown";
-
-export class FieldEncryptionBoundary {
- static classifyField(
- tableName: string,
- fieldName: string,
- ): FieldClassification {
- const policy = this.getPolicy(tableName);
- if (!policy) return "unknown";
- if (policy.sensitive.has(fieldName)) return "sensitive";
- if (policy.plaintext.has(fieldName)) return "plaintext";
- return "unknown";
- }
-
- static getSensitiveFields(tableName: string): string[] {
- const policy = this.getPolicy(tableName);
- return policy ? [...policy.sensitive].sort() : [];
- }
-
- static encryptRecord>(
- tableName: string,
- record: T,
- userDataKey: Buffer,
- recordId = record.id,
- ): T {
- const id = this.requireRecordId(recordId);
- const encryptedRecord: Record = { ...record };
-
- for (const fieldName of this.getSensitiveFields(tableName)) {
- const value = encryptedRecord[fieldName];
- if (typeof value === "string" && value) {
- encryptedRecord[fieldName] = FieldCrypto.encryptField(
- value,
- userDataKey,
- id,
- fieldName,
- );
- }
- }
-
- return encryptedRecord as T;
- }
-
- static decryptRecord>(
- tableName: string,
- record: T,
- userDataKey: Buffer,
- recordId = record.id,
- ): T {
- const id = this.requireRecordId(recordId);
- const decryptedRecord: Record = { ...record };
-
- for (const fieldName of this.getSensitiveFields(tableName)) {
- const value = decryptedRecord[fieldName];
- if (typeof value === "string" && value) {
- decryptedRecord[fieldName] = LazyFieldEncryption.safeGetFieldValue(
- value,
- userDataKey,
- id,
- fieldName,
- );
- }
- }
-
- return decryptedRecord as T;
- }
-
- private static getPolicy(tableName: string) {
- return FIELD_ENCRYPTION_POLICY[tableName as PolicyTable];
- }
-
- private static requireRecordId(recordId: unknown): string {
- if (recordId === null || recordId === undefined || recordId === "") {
- throw new Error("Field encryption requires a stable record id.");
- }
- return String(recordId);
- }
-}
diff --git a/src/backend/database/repositories/file-manager-bookmark-repository.ts b/src/backend/database/repositories/file-manager-bookmark-repository.ts
index dfd68b3f..351b2af7 100644
--- a/src/backend/database/repositories/file-manager-bookmark-repository.ts
+++ b/src/backend/database/repositories/file-manager-bookmark-repository.ts
@@ -5,6 +5,7 @@ import {
fileManagerShortcuts,
} from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect;
export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect;
@@ -112,7 +113,7 @@ export class FileManagerBookmarkRepository {
userId: string,
input: Pick,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerRecent)
.where(
and(
@@ -120,14 +121,13 @@ export class FileManagerBookmarkRepository {
eq(fileManagerRecent.hostId, input.hostId),
eq(fileManagerRecent.path, input.path),
),
- )
- .returning({ id: fileManagerRecent.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async listPinnedForHost(
@@ -199,7 +199,7 @@ export class FileManagerBookmarkRepository {
userId: string,
input: Pick,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerPinned)
.where(
and(
@@ -207,14 +207,13 @@ export class FileManagerBookmarkRepository {
eq(fileManagerPinned.hostId, input.hostId),
eq(fileManagerPinned.path, input.path),
),
- )
- .returning({ id: fileManagerPinned.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async listShortcutsForHost(
@@ -288,7 +287,7 @@ export class FileManagerBookmarkRepository {
userId: string,
input: Pick,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerShortcuts)
.where(
and(
@@ -296,14 +295,13 @@ export class FileManagerBookmarkRepository {
eq(fileManagerShortcuts.hostId, input.hostId),
eq(fileManagerShortcuts.path, input.path),
),
- )
- .returning({ id: fileManagerShortcuts.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
@@ -456,75 +454,66 @@ export class FileManagerBookmarkRepository {
}
private async deleteRecentByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerRecent)
- .where(eq(fileManagerRecent.userId, userId))
- .returning({ id: fileManagerRecent.id });
- return rows.length;
+ .where(eq(fileManagerRecent.userId, userId));
+ return rowsAffected(result);
}
private async deletePinnedByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerPinned)
- .where(eq(fileManagerPinned.userId, userId))
- .returning({ id: fileManagerPinned.id });
- return rows.length;
+ .where(eq(fileManagerPinned.userId, userId));
+ return rowsAffected(result);
}
private async deleteShortcutsByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerShortcuts)
- .where(eq(fileManagerShortcuts.userId, userId))
- .returning({ id: fileManagerShortcuts.id });
- return rows.length;
+ .where(eq(fileManagerShortcuts.userId, userId));
+ return rowsAffected(result);
}
private async deleteRecentByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerRecent)
- .where(eq(fileManagerRecent.hostId, hostId))
- .returning({ id: fileManagerRecent.id });
- return rows.length;
+ .where(eq(fileManagerRecent.hostId, hostId));
+ return rowsAffected(result);
}
private async deletePinnedByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerPinned)
- .where(eq(fileManagerPinned.hostId, hostId))
- .returning({ id: fileManagerPinned.id });
- return rows.length;
+ .where(eq(fileManagerPinned.hostId, hostId));
+ return rowsAffected(result);
}
private async deleteShortcutsByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerShortcuts)
- .where(eq(fileManagerShortcuts.hostId, hostId))
- .returning({ id: fileManagerShortcuts.id });
- return rows.length;
+ .where(eq(fileManagerShortcuts.hostId, hostId));
+ return rowsAffected(result);
}
private async deleteRecentByHostIds(hostIds: number[]): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerRecent)
- .where(inArray(fileManagerRecent.hostId, hostIds))
- .returning({ id: fileManagerRecent.id });
- return rows.length;
+ .where(inArray(fileManagerRecent.hostId, hostIds));
+ return rowsAffected(result);
}
private async deletePinnedByHostIds(hostIds: number[]): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerPinned)
- .where(inArray(fileManagerPinned.hostId, hostIds))
- .returning({ id: fileManagerPinned.id });
- return rows.length;
+ .where(inArray(fileManagerPinned.hostId, hostIds));
+ return rowsAffected(result);
}
private async deleteShortcutsByHostIds(hostIds: number[]): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(fileManagerShortcuts)
- .where(inArray(fileManagerShortcuts.hostId, hostIds))
- .returning({ id: fileManagerShortcuts.id });
- return rows.length;
+ .where(inArray(fileManagerShortcuts.hostId, hostIds));
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/homepage-item-repository.ts b/src/backend/database/repositories/homepage-item-repository.ts
index 2be7ebed..7f4a88ce 100644
--- a/src/backend/database/repositories/homepage-item-repository.ts
+++ b/src/backend/database/repositories/homepage-item-repository.ts
@@ -2,6 +2,12 @@ import { and, asc, eq } from "drizzle-orm";
import { randomUUID } from "crypto";
import { homepageItems } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type HomepageItemRecord = typeof homepageItems.$inferSelect;
@@ -35,18 +41,15 @@ export class HomepageItemRepository {
input: HomepageItemCreateInput,
now = new Date().toISOString(),
): Promise {
- const [created] = await this.context.drizzle
- .insert(homepageItems)
- .values({
- syncId: randomUUID(),
- userId,
- typeId: input.typeId,
- title: input.title,
- config: input.config,
- createdAt: now,
- updatedAt: now,
- })
- .returning();
+ const [created] = await insertReturning(this.context, homepageItems, {
+ syncId: randomUUID(),
+ userId,
+ typeId: input.typeId,
+ title: input.title,
+ config: input.config,
+ createdAt: now,
+ updatedAt: now,
+ });
await this.afterWrite();
return created;
@@ -71,11 +74,12 @@ export class HomepageItemRepository {
updates: HomepageItemUpdateInput,
updatedAt = new Date().toISOString(),
): Promise {
- const [updated] = await this.context.drizzle
- .update(homepageItems)
- .set({ ...updates, updatedAt })
- .where(and(eq(homepageItems.id, id), eq(homepageItems.userId, userId)))
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ homepageItems,
+ { ...updates, updatedAt },
+ and(eq(homepageItems.id, id), eq(homepageItems.userId, userId)),
+ );
if (updated) {
await this.afterWrite();
@@ -88,27 +92,27 @@ export class HomepageItemRepository {
userId: string,
id: number,
): Promise<{ syncId: string | null } | null> {
- const rows = await this.context.drizzle
- .delete(homepageItems)
- .where(and(eq(homepageItems.id, id), eq(homepageItems.userId, userId)))
- .returning({ syncId: homepageItems.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ homepageItems,
+ and(eq(homepageItems.id, id), eq(homepageItems.userId, userId)),
+ );
if (rows.length === 0) return null;
await this.afterWrite();
- return rows[0];
+ return { syncId: rows[0].syncId };
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(homepageItems)
- .where(eq(homepageItems.userId, userId))
- .returning({ id: homepageItems.id });
+ .where(eq(homepageItems.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/homepage-layout-repository.ts b/src/backend/database/repositories/homepage-layout-repository.ts
index bb10c453..d1e2d8c8 100644
--- a/src/backend/database/repositories/homepage-layout-repository.ts
+++ b/src/backend/database/repositories/homepage-layout-repository.ts
@@ -1,6 +1,8 @@
import { eq } from "drizzle-orm";
import { homepageLayouts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type HomepageLayoutRecord = typeof homepageLayouts.$inferSelect;
@@ -28,34 +30,35 @@ export class HomepageLayoutRepository {
const existing = await this.findByUserId(userId);
if (!existing) {
- const [created] = await this.context.drizzle
- .insert(homepageLayouts)
- .values({ userId, layout, updatedAt })
- .returning();
+ const [created] = await insertReturning(this.context, homepageLayouts, {
+ userId,
+ layout,
+ updatedAt,
+ });
await this.afterWrite();
return created;
}
- const [updated] = await this.context.drizzle
- .update(homepageLayouts)
- .set({ layout, updatedAt })
- .where(eq(homepageLayouts.userId, userId))
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ homepageLayouts,
+ { layout, updatedAt },
+ eq(homepageLayouts.userId, userId),
+ );
await this.afterWrite();
return updated;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(homepageLayouts)
- .where(eq(homepageLayouts.userId, userId))
- .returning({ id: homepageLayouts.id });
+ .where(eq(homepageLayouts.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/host-folder-repository.ts b/src/backend/database/repositories/host-folder-repository.ts
index d83df5a3..f7c739e4 100644
--- a/src/backend/database/repositories/host-folder-repository.ts
+++ b/src/backend/database/repositories/host-folder-repository.ts
@@ -3,6 +3,12 @@ import { randomUUID } from "crypto";
import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
import { hosts, sshCredentials, sshFolders } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type HostFolderRecord = typeof sshFolders.$inferSelect;
export type HostFolderHostRecord = typeof hosts.$inferSelect;
@@ -24,19 +30,31 @@ export class HostFolderRepository {
newName: string,
now = new Date().toISOString(),
): Promise {
+ // CAST target: every engine spells the text type differently enough to
+ // matter here — MySQL has no `text` cast and wants `char`.
+ const textType = this.context.dialect === "mysql" ? "char" : "text";
const oldPrefix = `${oldName} / `;
const newPrefix = `${newName} / `;
const childLike = `${oldPrefix}%`;
+ // CONCAT, not `||`: MySQL reads `||` as logical OR unless the server runs
+ // with PIPES_AS_CONCAT, so the child paths would have been rewritten to 0.
+ // No error, just wrong folder names. CONCAT and SUBSTR mean the same thing
+ // on all three engines.
+ //
+ // The prefix is inlined rather than bound: CONCAT is variadic, so Postgres
+ // cannot infer a parameter's type from its position and rejects the
+ // statement with 42P18 before it runs. The value is a folder name the
+ // caller supplied, so it goes through a bound placeholder in a plain
+ // concatenation instead of sql.raw.
const renameExpr = (col: SQLiteColumn) =>
- sql`CASE WHEN ${col} = ${oldName} THEN ${newName} ELSE ${newPrefix} || substr(${col}, ${oldPrefix.length + 1}) END`;
+ sql`CASE WHEN ${col} = ${oldName} THEN ${newName} ELSE CONCAT(CAST(${newPrefix} AS ${sql.raw(textType)}), SUBSTR(${col}, ${sql.raw(String(oldPrefix.length + 1))})) END`;
const folderMatch = (col: SQLiteColumn) =>
or(eq(col, oldName), like(col, childLike));
const updatedHosts = await this.context.drizzle
.update(hosts)
.set({ folder: renameExpr(hosts.folder), updatedAt: now })
- .where(and(eq(hosts.userId, userId), folderMatch(hosts.folder)))
- .returning({ id: hosts.id });
+ .where(and(eq(hosts.userId, userId), folderMatch(hosts.folder)));
const updatedCredentials = await this.context.drizzle
.update(sshCredentials)
@@ -46,8 +64,7 @@ export class HostFolderRepository {
eq(sshCredentials.userId, userId),
folderMatch(sshCredentials.folder),
),
- )
- .returning({ id: sshCredentials.id });
+ );
await this.context.drizzle
.update(sshFolders)
@@ -56,8 +73,8 @@ export class HostFolderRepository {
await this.afterWrite();
return {
- updatedHosts: updatedHosts.length,
- updatedCredentials: updatedCredentials.length,
+ updatedHosts: rowsAffected(updatedHosts),
+ updatedCredentials: rowsAffected(updatedCredentials),
};
}
@@ -78,35 +95,33 @@ export class HostFolderRepository {
): Promise<{ folder: HostFolderRecord; created: boolean }> {
const existing = await this.findFolder(userId, name);
if (existing) {
- const [updated] = await this.context.drizzle
- .update(sshFolders)
- .set({
+ const [updated] = await updateReturning(
+ this.context,
+ sshFolders,
+ {
color,
icon,
credentialId:
credentialId === undefined ? existing.credentialId : credentialId,
updatedAt: now,
- })
- .where(and(eq(sshFolders.userId, userId), eq(sshFolders.name, name)))
- .returning();
+ },
+ and(eq(sshFolders.userId, userId), eq(sshFolders.name, name)),
+ );
await this.afterWrite();
return { folder: updated, created: false };
}
- const [created] = await this.context.drizzle
- .insert(sshFolders)
- .values({
- syncId: randomUUID(),
- userId,
- name,
- color,
- icon,
- credentialId: credentialId ?? null,
- createdAt: now,
- updatedAt: now,
- })
- .returning();
+ const [created] = await insertReturning(this.context, sshFolders, {
+ syncId: randomUUID(),
+ userId,
+ name,
+ color,
+ icon,
+ credentialId: credentialId ?? null,
+ createdAt: now,
+ updatedAt: now,
+ });
await this.afterWrite();
return { folder: created, created: true };
@@ -139,10 +154,11 @@ export class HostFolderRepository {
.where(and(eq(hosts.userId, userId), folderMatch(hosts.folder)));
}
- const deletedFolders = await this.context.drizzle
- .delete(sshFolders)
- .where(and(eq(sshFolders.userId, userId), folderMatch(sshFolders.name)))
- .returning({ syncId: sshFolders.syncId });
+ const deletedFolders = await deleteReturning(
+ this.context,
+ sshFolders,
+ and(eq(sshFolders.userId, userId), folderMatch(sshFolders.name)),
+ );
await this.afterWrite();
@@ -157,16 +173,15 @@ export class HostFolderRepository {
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sshFolders)
- .where(eq(sshFolders.userId, userId))
- .returning({ id: sshFolders.id });
+ .where(eq(sshFolders.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async findFolder(
diff --git a/src/backend/database/repositories/host-health-repository.ts b/src/backend/database/repositories/host-health-repository.ts
index 5aac9308..875e1d74 100644
--- a/src/backend/database/repositories/host-health-repository.ts
+++ b/src/backend/database/repositories/host-health-repository.ts
@@ -1,6 +1,8 @@
-import { and, desc, eq } from "drizzle-orm";
+import { and, desc, eq, notInArray } from "drizzle-orm";
import { hostHealthChecks, hostHealthHistory } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type HostHealthCheckRecord = typeof hostHealthChecks.$inferSelect;
export type HostHealthHistoryRecord = typeof hostHealthHistory.$inferSelect;
@@ -45,27 +47,25 @@ export class HostHealthRepository {
): Promise {
const existing = await this.findChecksByUserAndHost(userId, hostId);
if (existing) {
- const [updated] = await this.context.drizzle
- .update(hostHealthChecks)
- .set({ checks, intervalSeconds, updatedAt: now })
- .where(eq(hostHealthChecks.id, existing.id))
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ hostHealthChecks,
+ { checks, intervalSeconds, updatedAt: now },
+ eq(hostHealthChecks.id, existing.id),
+ );
await this.afterWrite();
return updated;
}
- const [created] = await this.context.drizzle
- .insert(hostHealthChecks)
- .values({
- userId,
- hostId,
- checks,
- intervalSeconds,
- createdAt: now,
- updatedAt: now,
- })
- .returning();
+ const [created] = await insertReturning(this.context, hostHealthChecks, {
+ userId,
+ hostId,
+ checks,
+ intervalSeconds,
+ createdAt: now,
+ updatedAt: now,
+ });
await this.afterWrite();
return created;
@@ -94,7 +94,7 @@ export class HostHealthRepository {
})),
);
- this.pruneHistory(userId, hostId, keep);
+ await this.pruneHistory(userId, hostId, keep);
await this.afterWrite();
return results.length;
}
@@ -121,41 +121,55 @@ export class HostHealthRepository {
checksDeleted: number;
historyDeleted: number;
}> {
- const historyRows = await this.context.drizzle
+ const historyResult = await this.context.drizzle
.delete(hostHealthHistory)
- .where(eq(hostHealthHistory.userId, userId))
- .returning({ id: hostHealthHistory.id });
+ .where(eq(hostHealthHistory.userId, userId));
- const checkRows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostHealthChecks)
- .where(eq(hostHealthChecks.userId, userId))
- .returning({ id: hostHealthChecks.id });
+ .where(eq(hostHealthChecks.userId, userId));
- if (historyRows.length > 0 || checkRows.length > 0) {
+ if (rowsAffected(historyResult) > 0 || rowsAffected(result) > 0) {
await this.afterWrite();
}
return {
- checksDeleted: checkRows.length,
- historyDeleted: historyRows.length,
+ checksDeleted: rowsAffected(result),
+ historyDeleted: rowsAffected(historyResult),
};
}
- private pruneHistory(userId: string, hostId: number, keep: number): void {
- this.context.sqlite
- ?.prepare(
- `DELETE FROM host_health_history
- WHERE id IN (
- SELECT id FROM host_health_history
- WHERE user_id = ? AND host_id = ?
- AND id NOT IN (
- SELECT id FROM host_health_history
- WHERE user_id = ? AND host_id = ?
- ORDER BY ts DESC LIMIT ?
- )
- )`,
- )
- .run(userId, hostId, userId, hostId, keep);
+ /** Keeps the newest `keep` rows for the host and drops the rest. */
+ private async pruneHistory(
+ userId: string,
+ hostId: number,
+ keep: number,
+ ): Promise {
+ const scope = and(
+ eq(hostHealthHistory.userId, userId),
+ eq(hostHealthHistory.hostId, hostId),
+ );
+
+ const retained = await this.context.drizzle
+ .select({ id: hostHealthHistory.id })
+ .from(hostHealthHistory)
+ .where(scope)
+ .orderBy(desc(hostHealthHistory.ts))
+ .limit(keep);
+
+ // Nothing retained means nothing to keep back, so the scope alone is the
+ // delete condition.
+ await this.context.drizzle.delete(hostHealthHistory).where(
+ retained.length
+ ? and(
+ scope,
+ notInArray(
+ hostHealthHistory.id,
+ retained.map((row) => row.id),
+ ),
+ )
+ : scope,
+ );
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/host-metrics-history-repository.ts b/src/backend/database/repositories/host-metrics-history-repository.ts
index cbad4e2f..62bdb0e8 100644
--- a/src/backend/database/repositories/host-metrics-history-repository.ts
+++ b/src/backend/database/repositories/host-metrics-history-repository.ts
@@ -1,6 +1,7 @@
-import { and, asc, eq, gte, lte } from "drizzle-orm";
+import { and, asc, eq, gte, lt, lte } from "drizzle-orm";
import { hostMetricsHistory } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { sqlTimestampDaysAgo } from "./sql-timestamp.js";
export type HostMetricsHistoryRecord = typeof hostMetricsHistory.$inferSelect;
@@ -32,12 +33,15 @@ export class HostMetricsHistoryRepository {
await this.afterWrite();
}
- pruneOlderThan(hostId: number, retentionDays: number): void {
- this.context.sqlite
- ?.prepare(
- "DELETE FROM host_metrics_history WHERE host_id = ? AND ts < datetime('now', ?)",
- )
- .run(hostId, `-${retentionDays} days`);
+ async pruneOlderThan(hostId: number, retentionDays: number): Promise {
+ await this.context.drizzle
+ .delete(hostMetricsHistory)
+ .where(
+ and(
+ eq(hostMetricsHistory.hostId, hostId),
+ lt(hostMetricsHistory.ts, sqlTimestampDaysAgo(retentionDays)),
+ ),
+ );
}
async listRange(
diff --git a/src/backend/database/repositories/host-metrics-preference-repository.ts b/src/backend/database/repositories/host-metrics-preference-repository.ts
index 070063d7..108acf8a 100644
--- a/src/backend/database/repositories/host-metrics-preference-repository.ts
+++ b/src/backend/database/repositories/host-metrics-preference-repository.ts
@@ -1,6 +1,8 @@
import { and, eq } from "drizzle-orm";
import { hostMetricsPreferences, hosts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type HostMetricsPreferenceRecord =
typeof hostMetricsPreferences.$inferSelect;
@@ -37,26 +39,28 @@ export class HostMetricsPreferenceRepository {
): Promise {
const existing = await this.findByUserAndHost(userId, hostId);
if (existing) {
- const [updated] = await this.context.drizzle
- .update(hostMetricsPreferences)
- .set({ layout, updatedAt: now })
- .where(eq(hostMetricsPreferences.id, existing.id))
- .returning();
+ const [updated] = await updateReturning(
+ this.context,
+ hostMetricsPreferences,
+ { layout, updatedAt: now },
+ eq(hostMetricsPreferences.id, existing.id),
+ );
await this.afterWrite();
return updated;
}
- const [created] = await this.context.drizzle
- .insert(hostMetricsPreferences)
- .values({
+ const [created] = await insertReturning(
+ this.context,
+ hostMetricsPreferences,
+ {
userId,
hostId,
layout,
createdAt: now,
updatedAt: now,
- })
- .returning();
+ },
+ );
await this.afterWrite();
return created;
@@ -67,28 +71,26 @@ export class HostMetricsPreferenceRepository {
hostId: number,
statsConfig: string,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(hosts)
.set({ statsConfig })
- .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
- .returning({ id: hosts.id });
+ .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)));
- if (rows.length === 0) return false;
+ if (rowsAffected(result) === 0) return false;
await this.afterWrite();
return true;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostMetricsPreferences)
- .where(eq(hostMetricsPreferences.userId, userId))
- .returning({ id: hostMetricsPreferences.id });
+ .where(eq(hostMetricsPreferences.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/host-repository.ts b/src/backend/database/repositories/host-repository.ts
index c7d2cf72..e3319444 100644
--- a/src/backend/database/repositories/host-repository.ts
+++ b/src/backend/database/repositories/host-repository.ts
@@ -3,6 +3,12 @@ import { randomUUID } from "crypto";
import { hostAccess, hosts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type HostRecord = typeof hosts.$inferSelect;
export type NewHostRecord = typeof hosts.$inferInsert;
@@ -21,10 +27,10 @@ export class HostRepository {
) {}
async create(host: NewHostRecord): Promise {
- const rows = await this.context.drizzle
- .insert(hosts)
- .values({ syncId: randomUUID(), ...host })
- .returning();
+ const rows = await insertReturning(this.context, hosts, {
+ syncId: randomUUID(),
+ ...host,
+ });
await this.afterWrite();
return rows[0];
}
@@ -51,10 +57,11 @@ export class HostRepository {
delete (encryptedHost as Partial).id;
}
- const rows = await this.context.drizzle
- .insert(hosts)
- .values(encryptedHost as NewHostRecord)
- .returning();
+ const rows = await insertReturning(
+ this.context,
+ hosts,
+ encryptedHost as NewHostRecord,
+ );
await this.afterWrite();
return DataCrypto.decryptRecord("ssh_data", rows[0], userId, userDataKey);
@@ -150,11 +157,12 @@ export class HostRepository {
hostId: number,
update: HostUpdate,
): Promise {
- const rows = await this.context.drizzle
- .update(hosts)
- .set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
- .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ hosts,
+ { ...update, updatedAt: sql`CURRENT_TIMESTAMP` },
+ and(eq(hosts.id, hostId), eq(hosts.userId, userId)),
+ );
await this.afterWrite();
return rows[0] ?? null;
@@ -173,11 +181,12 @@ export class HostRepository {
userDataKey,
);
- const rows = await this.context.drizzle
- .update(hosts)
- .set({ ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` })
- .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ hosts,
+ { ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` },
+ and(eq(hosts.id, hostId), eq(hosts.userId, userId)),
+ );
await this.afterWrite();
return rows[0]
@@ -213,17 +222,16 @@ export class HostRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(hosts)
.set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
- .where(and(inArray(hosts.id, hostIds), eq(hosts.userId, userId)))
- .returning({ id: hosts.id });
+ .where(and(inArray(hosts.id, hostIds), eq(hosts.userId, userId)));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteForUser(
@@ -232,39 +240,38 @@ export class HostRepository {
): Promise<{ syncId: string | null } | null> {
await this.deleteAccessForHost(hostId);
- const rows = await this.context.drizzle
- .delete(hosts)
- .where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
- .returning({ syncId: hosts.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ hosts,
+ and(eq(hosts.id, hostId), eq(hosts.userId, userId)),
+ );
await this.afterWrite();
- return rows[0] ?? null;
+ return rows[0] ? { syncId: rows[0].syncId } : null;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hosts)
- .where(eq(hosts.userId, userId))
- .returning({ id: hosts.id });
+ .where(eq(hosts.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteAccessForHost(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostAccess)
- .where(eq(hostAccess.hostId, hostId))
- .returning({ id: hostAccess.id });
+ .where(eq(hostAccess.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/host-resolution-repository.ts b/src/backend/database/repositories/host-resolution-repository.ts
index 31926f0b..93946159 100644
--- a/src/backend/database/repositories/host-resolution-repository.ts
+++ b/src/backend/database/repositories/host-resolution-repository.ts
@@ -1,5 +1,5 @@
import { and, eq, inArray, isNotNull } from "drizzle-orm";
-import { hostAccess, hosts, sshCredentials, sshFolders } from "../db/schema.js";
+import { hosts, sshCredentials, sshFolders } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js";
@@ -26,6 +26,28 @@ export interface HostListAccessEntry {
permissionLevel: string;
expiresAt: string | null;
}
+
+const HOST_PERMISSION_RANK: Record = {
+ connect: 1,
+ view: 2,
+ edit: 3,
+ manage: 4,
+};
+
+function preferHostAccess(
+ current: HostListAccessEntry,
+ candidate: HostListAccessEntry,
+): HostListAccessEntry {
+ const currentRank = HOST_PERMISSION_RANK[current.permissionLevel] ?? 0;
+ const candidateRank = HOST_PERMISSION_RANK[candidate.permissionLevel] ?? 0;
+ if (candidateRank !== currentRank) {
+ return candidateRank > currentRank ? candidate : current;
+ }
+ if (current.expiresAt === null) return current;
+ if (candidate.expiresAt === null) return candidate;
+ return candidate.expiresAt > current.expiresAt ? candidate : current;
+}
+
export type HostListRow = HostResolutionHostRecord & {
ownerId: string;
isShared: boolean;
@@ -103,9 +125,15 @@ export class HostResolutionRepository {
.from(hosts)
.where(eq(hosts.userId, userId));
- const sharedHostIds = Array.from(
- new Set(accessEntries.map((access) => access.hostId)),
- );
+ const accessByHostId = new Map();
+ for (const access of accessEntries) {
+ const current = accessByHostId.get(access.hostId);
+ accessByHostId.set(
+ access.hostId,
+ current ? preferHostAccess(current, access) : access,
+ );
+ }
+ const sharedHostIds = Array.from(accessByHostId.keys());
const sharedHostRows =
sharedHostIds.length > 0
? await this.context.drizzle
@@ -125,7 +153,7 @@ export class HostResolutionRepository {
permissionLevel: undefined,
expiresAt: undefined,
})),
- ...accessEntries.flatMap((access) => {
+ ...Array.from(accessByHostId.values()).flatMap((access) => {
const host = sharedHostsById.get(access.hostId);
if (!host || host.userId === userId) {
return [];
@@ -302,19 +330,6 @@ export class HostResolutionRepository {
return this.decryptOne("ssh_credentials", rows[0], decryptUserId);
}
- async findOverrideCredentialId(
- hostId: number,
- userId: string,
- ): Promise {
- const rows = await this.context.drizzle
- .select({ overrideCredentialId: hostAccess.overrideCredentialId })
- .from(hostAccess)
- .where(and(eq(hostAccess.hostId, hostId), eq(hostAccess.userId, userId)))
- .limit(1);
-
- return rows[0]?.overrideCredentialId ?? null;
- }
-
/**
* Resolve the nearest assigned credential for a folder path, walking up
* through parent folders (e.g. "Switches / Floor1" falls back to
diff --git a/src/backend/database/repositories/mutation-result.ts b/src/backend/database/repositories/mutation-result.ts
new file mode 100644
index 00000000..d849c774
--- /dev/null
+++ b/src/backend/database/repositories/mutation-result.ts
@@ -0,0 +1,157 @@
+import type { DatabaseDialect } from "../db/dialect.js";
+
+/**
+ * Reading the outcome of a write without depending on RETURNING.
+ *
+ * SQLite and Postgres can attach `.returning()` to a delete or update and get
+ * the affected rows back. **MySQL cannot** — it has no RETURNING clause, and
+ * drizzle's mysql-core does not expose the method at all, so the call is a
+ * TypeError rather than a bad query. 175 call sites here read a write's result,
+ * so the difference has to be absorbed somewhere.
+ *
+ * The split that matters is what the caller actually needs:
+ *
+ * - **How many rows changed** — the majority, and none of them need the rows.
+ * They used to ask for them anyway, via `.returning().length`. Dropping the
+ * `.returning()` and reading the driver's own count is both portable and one
+ * less thing for the database to send back.
+ * - **The rows themselves** — cannot be emulated on MySQL without reading
+ * first, which needs a transaction to stay correct under concurrency. Those
+ * call sites are handled individually rather than behind a helper that hides
+ * an extra round trip.
+ */
+
+/**
+ * The count each driver reports for a write, under its own name.
+ *
+ * Every engine says how many rows a write touched. None of them agree on what
+ * to call it:
+ *
+ * | driver | shape |
+ * |----------------|----------------------------------------|
+ * | better-sqlite3 | `{ changes, lastInsertRowid }` |
+ * | node-postgres | `{ rowCount, rows, command }` |
+ * | mysql2 | `[{ affectedRows, insertId }, fields]` |
+ *
+ * These are the shapes returned when NO `.returning()` is attached — which is
+ * the portable way to write, since MySQL has no RETURNING clause at all.
+ */
+interface WriteHeader {
+ changes?: number;
+ rowCount?: number;
+ affectedRows?: number;
+ lastInsertRowid?: number | bigint;
+ insertId?: number;
+}
+
+const COUNT_FIELDS = ["changes", "rowCount", "affectedRows"] as const;
+
+/**
+ * mysql2 hands back `[ResultSetHeader, fields]`, which is itself an array — so
+ * "is it an array" cannot distinguish a write header from a returning() result.
+ * The header is identified by carrying one of the fields above instead.
+ */
+function asWriteHeader(result: unknown): WriteHeader | null {
+ const candidate =
+ Array.isArray(result) && result.length > 0 ? result[0] : result;
+
+ if (!candidate || typeof candidate !== "object") return null;
+ const header = candidate as WriteHeader;
+
+ const known =
+ COUNT_FIELDS.some((field) => typeof header[field] === "number") ||
+ typeof header.insertId === "number" ||
+ typeof header.lastInsertRowid === "number" ||
+ typeof header.lastInsertRowid === "bigint";
+
+ return known ? header : null;
+}
+
+/**
+ * Number of rows a write touched.
+ *
+ * Pass the result of the write itself — every driver's header is understood, so
+ * the caller neither branches on the dialect nor attaches `.returning()` just to
+ * count what came back.
+ *
+ * A `.returning()` array is still accepted, for the call sites that need the
+ * rows for their own reasons and would rather not count them twice.
+ */
+export function rowsAffected(result: unknown): number {
+ const header = asWriteHeader(result);
+ if (header) {
+ for (const field of COUNT_FIELDS) {
+ const count = header[field];
+ if (typeof count === "number") return count;
+ }
+ // A header with only insertId: one row went in.
+ return 0;
+ }
+
+ if (Array.isArray(result)) return result.length;
+ return 0;
+}
+
+/**
+ * Id assigned by an insert.
+ *
+ * **Only meaningful on the result of an insert.** SQLite's `lastInsertRowid` and
+ * MySQL's `insertId` are connection-level values that survive the statement that
+ * set them — after a delete, SQLite still reports whatever the last insert
+ * produced. Passing an update or delete result here gets a stale id, not null.
+ *
+ * Returns null when the table has no autoincrement key.
+ */
+export function insertedId(result: unknown): number | null {
+ const header = asWriteHeader(result);
+ if (header) {
+ // MySQL and SQLite both use 0 for "no autoincrement column".
+ if (typeof header.insertId === "number") {
+ return header.insertId > 0 ? header.insertId : null;
+ }
+ if (typeof header.lastInsertRowid === "bigint") {
+ return header.lastInsertRowid > 0n
+ ? Number(header.lastInsertRowid)
+ : null;
+ }
+ if (typeof header.lastInsertRowid === "number") {
+ return header.lastInsertRowid > 0 ? header.lastInsertRowid : null;
+ }
+ return null;
+ }
+
+ if (Array.isArray(result)) {
+ const first = result[0] as { id?: unknown } | undefined;
+ return typeof first?.id === "number" ? first.id : null;
+ }
+
+ return null;
+}
+
+/**
+ * Whether `.returning()` can be attached to a write on this engine.
+ *
+ * Call sites that genuinely need the affected rows use this to choose between
+ * one statement and a read-then-write inside a transaction.
+ */
+export function supportsReturning(dialect: DatabaseDialect): boolean {
+ return dialect !== "mysql";
+}
+
+/**
+ * Reads an aggregate count as a number.
+ *
+ * `sql` is a type assertion, not a conversion. Postgres returns COUNT()
+ * as bigint, which node-postgres hands back as a **string** so that values past
+ * 2^53 survive — so the annotation is a lie there and comparisons like
+ * `count < max` compare a string to a number.
+ */
+export function countValue(value: unknown): number {
+ if (typeof value === "number") return value;
+ if (typeof value === "bigint") return Number(value);
+ if (typeof value === "string") {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : 0;
+ }
+ return 0;
+}
diff --git a/src/backend/database/repositories/network-topology-repository.ts b/src/backend/database/repositories/network-topology-repository.ts
index fa8024fb..074c442a 100644
--- a/src/backend/database/repositories/network-topology-repository.ts
+++ b/src/backend/database/repositories/network-topology-repository.ts
@@ -1,6 +1,7 @@
import { eq } from "drizzle-orm";
import { networkTopology } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type NetworkTopologyRecord = typeof networkTopology.$inferSelect;
@@ -45,16 +46,15 @@ export class NetworkTopologyRepository {
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(networkTopology)
- .where(eq(networkTopology.userId, userId))
- .returning({ id: networkTopology.id });
+ .where(eq(networkTopology.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/open-tab-repository.ts b/src/backend/database/repositories/open-tab-repository.ts
index 5cad21de..832386c0 100644
--- a/src/backend/database/repositories/open-tab-repository.ts
+++ b/src/backend/database/repositories/open-tab-repository.ts
@@ -1,6 +1,7 @@
import { and, eq, gt } from "drizzle-orm";
import { userOpenTabs } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type OpenTabRecord = typeof userOpenTabs.$inferSelect;
export type NewOpenTabRecord = typeof userOpenTabs.$inferInsert;
@@ -111,43 +112,40 @@ export class OpenTabRepository {
update: OpenTabUpdate,
updatedAt = new Date().toISOString(),
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(userOpenTabs)
.set({ ...update, updatedAt })
- .where(and(eq(userOpenTabs.id, id), eq(userOpenTabs.userId, userId)))
- .returning({ id: userOpenTabs.id });
+ .where(and(eq(userOpenTabs.id, id), eq(userOpenTabs.userId, userId)));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteForUser(userId: string, id: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(userOpenTabs)
- .where(and(eq(userOpenTabs.id, id), eq(userOpenTabs.userId, userId)))
- .returning({ id: userOpenTabs.id });
+ .where(and(eq(userOpenTabs.id, id), eq(userOpenTabs.userId, userId)));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(userOpenTabs)
- .where(eq(userOpenTabs.userId, userId))
- .returning({ id: userOpenTabs.id });
+ .where(eq(userOpenTabs.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async findByIdForUser(
diff --git a/src/backend/database/repositories/opkssh-token-repository.ts b/src/backend/database/repositories/opkssh-token-repository.ts
index 0c15bddd..e802ef88 100644
--- a/src/backend/database/repositories/opkssh-token-repository.ts
+++ b/src/backend/database/repositories/opkssh-token-repository.ts
@@ -1,6 +1,8 @@
import { and, eq } from "drizzle-orm";
import { opksshTokens } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { upsert } from "./returning.js";
export type OpksshTokenRecord = typeof opksshTokens.$inferSelect;
@@ -26,9 +28,10 @@ export class OpksshTokenRepository {
async upsert(input: OpksshTokenUpsertInput): Promise {
const createdAt = input.createdAt ?? new Date().toISOString();
- await this.context.drizzle
- .insert(opksshTokens)
- .values({
+ await upsert(
+ this.context,
+ opksshTokens,
+ {
userId: input.userId,
hostId: input.hostId,
sshCert: input.sshCert,
@@ -38,8 +41,8 @@ export class OpksshTokenRepository {
issuer: input.issuer,
audience: input.audience,
expiresAt: input.expiresAt,
- })
- .onConflictDoUpdate({
+ },
+ {
target: [opksshTokens.userId, opksshTokens.hostId],
set: {
sshCert: input.sshCert,
@@ -51,7 +54,8 @@ export class OpksshTokenRepository {
expiresAt: input.expiresAt,
createdAt,
},
- });
+ },
+ );
await this.afterWrite();
}
@@ -76,47 +80,44 @@ export class OpksshTokenRepository {
hostId: number,
lastUsed = new Date().toISOString(),
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(opksshTokens)
.set({ lastUsed })
.where(
and(eq(opksshTokens.userId, userId), eq(opksshTokens.hostId, hostId)),
- )
- .returning({ id: opksshTokens.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserAndHost(userId: string, hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(opksshTokens)
.where(
and(eq(opksshTokens.userId, userId), eq(opksshTokens.hostId, hostId)),
- )
- .returning({ id: opksshTokens.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(opksshTokens)
- .where(eq(opksshTokens.userId, userId))
- .returning({ id: opksshTokens.id });
+ .where(eq(opksshTokens.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/rbac-access-repository.ts b/src/backend/database/repositories/rbac-access-repository.ts
index 92a2463e..ec8aafe7 100644
--- a/src/backend/database/repositories/rbac-access-repository.ts
+++ b/src/backend/database/repositories/rbac-access-repository.ts
@@ -9,6 +9,8 @@ import {
users,
} from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type RbacAccessTargetType = "user" | "role";
@@ -156,7 +158,7 @@ export class RbacAccessRepository {
return { id: existing.id, created: false };
}
- const result = await this.context.drizzle.insert(hostAccess).values({
+ const [created] = await insertReturning(this.context, hostAccess, {
hostId: input.hostId,
userId: input.targetType === "user" ? input.targetUserId : null,
roleId: input.targetType === "role" ? input.targetRoleId : null,
@@ -166,7 +168,7 @@ export class RbacAccessRepository {
});
await this.afterWrite();
- return { id: Number(result.lastInsertRowid), created: true };
+ return { id: created.id, created: true };
}
async revokeHostAccess(accessId: number, hostId: number): Promise {
@@ -177,16 +179,15 @@ export class RbacAccessRepository {
}
async deleteHostAccessForHost(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostAccess)
- .where(eq(hostAccess.hostId, hostId))
- .returning({ id: hostAccess.id });
+ .where(eq(hostAccess.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteHostAccessForHosts(hostIds: number[]): Promise {
@@ -194,30 +195,27 @@ export class RbacAccessRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostAccess)
- .where(inArray(hostAccess.hostId, hostIds))
- .returning({ id: hostAccess.id });
+ .where(inArray(hostAccess.hostId, hostIds));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteHostAccessForUserReferences(userId: string): Promise {
- const directRows = await this.context.drizzle
+ const directResult = await this.context.drizzle
.delete(hostAccess)
- .where(eq(hostAccess.userId, userId))
- .returning({ id: hostAccess.id });
+ .where(eq(hostAccess.userId, userId));
- const grantedRows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostAccess)
- .where(eq(hostAccess.grantedBy, userId))
- .returning({ id: hostAccess.id });
+ .where(eq(hostAccess.grantedBy, userId));
- const deletedCount = directRows.length + grantedRows.length;
+ const deletedCount = rowsAffected(directResult) + rowsAffected(result);
if (deletedCount > 0) {
await this.afterWrite();
}
@@ -238,17 +236,6 @@ export class RbacAccessRepository {
return rows[0] ?? null;
}
- async updateHostAccessOverrideCredential(
- accessId: number,
- credentialId: number | null,
- ): Promise {
- await this.context.drizzle
- .update(hostAccess)
- .set({ overrideCredentialId: credentialId })
- .where(eq(hostAccess.id, accessId));
- await this.afterWrite();
- }
-
async listSnippetAccess(snippetId: number): Promise {
const rows = await this.context.drizzle
.select({
@@ -291,7 +278,7 @@ export class RbacAccessRepository {
return { id: existing.id, created: false };
}
- const result = await this.context.drizzle.insert(snippetAccess).values({
+ const [created] = await insertReturning(this.context, snippetAccess, {
snippetId: input.snippetId,
userId: input.targetType === "user" ? input.targetUserId : null,
roleId: input.targetType === "role" ? input.targetRoleId : null,
@@ -301,7 +288,7 @@ export class RbacAccessRepository {
});
await this.afterWrite();
- return { id: Number(result.lastInsertRowid), created: true };
+ return { id: created.id, created: true };
}
async revokeSnippetAccess(
@@ -512,21 +499,20 @@ export class RbacAccessRepository {
async deleteExpiredHostAccess(
now = new Date().toISOString(),
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(hostAccess)
.where(
and(
sql`${hostAccess.expiresAt} IS NOT NULL`,
sql`${hostAccess.expiresAt} <= ${now}`,
),
- )
- .returning({ id: hostAccess.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async findActiveHostAccess(
@@ -635,17 +621,16 @@ export class RbacAccessRepository {
hostId: number,
update: { permissionLevel?: string; expiresAt?: string | null },
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(hostAccess)
.set(update)
- .where(and(eq(hostAccess.id, accessId), eq(hostAccess.hostId, hostId)))
- .returning({ id: hostAccess.id });
+ .where(and(eq(hostAccess.id, accessId), eq(hostAccess.hostId, hostId)));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async findHostAccessOwnerId(hostAccessId: number): Promise {
diff --git a/src/backend/database/repositories/recent-activity-repository.ts b/src/backend/database/repositories/recent-activity-repository.ts
index bd7979fc..30b5fd09 100644
--- a/src/backend/database/repositories/recent-activity-repository.ts
+++ b/src/backend/database/repositories/recent-activity-repository.ts
@@ -1,6 +1,8 @@
import { desc, eq, inArray } from "drizzle-orm";
import { recentActivity } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type RecentActivityRecord = typeof recentActivity.$inferSelect;
export type NewRecentActivityRecord = typeof recentActivity.$inferInsert;
@@ -26,10 +28,7 @@ export class RecentActivityRepository {
async create(
activity: NewRecentActivityRecord,
): Promise {
- const rows = await this.context.drizzle
- .insert(recentActivity)
- .values(activity)
- .returning();
+ const rows = await insertReturning(this.context, recentActivity, activity);
await this.afterWrite();
return rows[0];
@@ -51,42 +50,39 @@ export class RecentActivityRepository {
return 0;
}
- const deletedRows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(recentActivity)
- .where(inArray(recentActivity.id, idsToDelete))
- .returning({ id: recentActivity.id });
+ .where(inArray(recentActivity.id, idsToDelete));
- if (deletedRows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return deletedRows.length;
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(recentActivity)
- .where(eq(recentActivity.userId, userId))
- .returning({ id: recentActivity.id });
+ .where(eq(recentActivity.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(recentActivity)
- .where(eq(recentActivity.hostId, hostId))
- .returning({ id: recentActivity.id });
+ .where(eq(recentActivity.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostIds(hostIds: number[]): Promise {
@@ -94,16 +90,15 @@ export class RecentActivityRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(recentActivity)
- .where(inArray(recentActivity.hostId, hostIds))
- .returning({ id: recentActivity.id });
+ .where(inArray(recentActivity.hostId, hostIds));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/returning.ts b/src/backend/database/repositories/returning.ts
new file mode 100644
index 00000000..fe2e05b1
--- /dev/null
+++ b/src/backend/database/repositories/returning.ts
@@ -0,0 +1,227 @@
+import { eq, type SQL } from "drizzle-orm";
+import type { SQLiteColumn, SQLiteTable } from "drizzle-orm/sqlite-core";
+import type { DatabaseContext } from "./database-context.js";
+import {
+ insertedId,
+ rowsAffected,
+ supportsReturning,
+} from "./mutation-result.js";
+
+/**
+ * Writes that need the affected rows back.
+ *
+ * `mutation-result.ts` covers the call sites that only wanted a count. These are
+ * the ones that genuinely read the rows — an updated record to return to the
+ * caller, a deleted row's fields to clean up alongside it.
+ *
+ * SQLite and Postgres do this in one statement with RETURNING. MySQL has no
+ * such clause, so the read is a second statement, and the pair has to be atomic:
+ *
+ * - **insert** — write, then read the row back by its key.
+ * - **update** — write, then read. Reading first would return the old values.
+ * - **delete** — read, then write. Reading after would return nothing.
+ *
+ * Both run in a transaction. Without one, a concurrent write between the two
+ * statements makes the returned rows describe a state that never existed, and
+ * with a connection pool the second statement might not even reach the same
+ * connection.
+ *
+ * ## The trap, and why it cannot bite silently
+ *
+ * On MySQL the update path re-reads using the same `where`. If the update
+ * changes a column that `where` tests, the read finds nothing — SQLite would
+ * have returned the row. Every current caller filters on an id it does not
+ * modify, but that is a convention, not a guarantee, so the mismatch is
+ * detected and thrown rather than returned as an empty array. Same for an
+ * insert whose row cannot be read back.
+ *
+ * Row types come from the table, so call sites keep the typing they had with
+ * `.returning()` and nothing has to be annotated by hand.
+ */
+
+/**
+ * What `.set()` accepts: a column's own type, or a SQL expression in its place —
+ * `updatedAt: sql`CURRENT_TIMESTAMP`` is the common one here.
+ */
+type UpdateValues = {
+ [K in keyof T["$inferInsert"]]?: T["$inferInsert"][K] | SQL;
+};
+
+export async function updateReturning(
+ context: DatabaseContext,
+ table: T,
+ values: UpdateValues,
+ where: SQL,
+): Promise {
+ const db = context.drizzle;
+
+ if (supportsReturning(context.dialect)) {
+ // The cast resolves a conditional in drizzle's return type that TypeScript
+ // cannot narrow while T is still generic. The runtime shape is the rows.
+ return db.update(table).set(values).where(where).returning() as Promise<
+ T["$inferSelect"][]
+ >;
+ }
+
+ return db.transaction(async (tx) => {
+ const written = await tx.update(table).set(values).where(where);
+ const rows = await tx.select().from(table).where(where);
+
+ // The trap this catches: if the update changed a column that `where` tests,
+ // the read finds nothing and the caller gets [] — on MySQL only, with no
+ // error, where SQLite would have returned the row. Rows changed but none
+ // readable back is exactly that case, so make it loud instead.
+ if (rows.length === 0 && rowsAffected(written) > 0) {
+ throw new Error(
+ `updateReturning wrote ${rowsAffected(written)} row(s) but could not read ` +
+ `them back: the update changed a column the where clause filters on. ` +
+ `Read the rows first, or filter on a column the update leaves alone.`,
+ );
+ }
+
+ return rows;
+ });
+}
+
+export async function deleteReturning(
+ context: DatabaseContext,
+ table: T,
+ where: SQL,
+): Promise {
+ const db = context.drizzle;
+
+ if (supportsReturning(context.dialect)) {
+ return db.delete(table).where(where).returning() as Promise<
+ T["$inferSelect"][]
+ >;
+ }
+
+ return db.transaction(async (tx) => {
+ const rows = await tx.select().from(table).where(where);
+ await tx.delete(table).where(where);
+ return rows;
+ });
+}
+
+/** A table this can read a single row back from. */
+type Keyed = SQLiteTable & { id: SQLiteColumn };
+
+/**
+ * Inserts one row and returns it as stored, including whatever the database
+ * filled in — defaults, an autoincrement id, a CURRENT_TIMESTAMP.
+ *
+ * This is the one case Postgres cannot shortcut either: without RETURNING there
+ * is no id to read back by. Hence the split is genuinely three-way — except
+ * that sqlite and pg both have RETURNING, so it collapses to two again.
+ *
+ * On MySQL the key comes from one of two places:
+ *
+ * - the caller supplied it (tables keyed by a text id, like `users`)
+ * - the engine assigned it, reported as `insertId`
+ *
+ * Restricted to tables with an `id` column, so a table keyed some other way is
+ * a compile error here rather than a row that silently fails to come back.
+ */
+export async function insertReturning(
+ context: DatabaseContext,
+ table: T,
+ values: T["$inferInsert"],
+): Promise {
+ const db = context.drizzle;
+
+ if (supportsReturning(context.dialect)) {
+ return db.insert(table).values(values).returning() as Promise<
+ T["$inferSelect"][]
+ >;
+ }
+
+ return db.transaction(async (tx) => {
+ const result = await tx.insert(table).values(values);
+
+ const supplied = (values as { id?: string | number }).id;
+ const key = supplied ?? insertedId(result);
+ if (key === null || key === undefined) {
+ throw new Error(
+ `Insert into ${String(table)} returned no id to read the row back by.`,
+ );
+ }
+
+ const rows = await tx.select().from(table).where(eq(table.id, key));
+ if (rows.length === 0) {
+ throw new Error(
+ `Inserted into ${String(table)} but could not read the row back by id ${key}.`,
+ );
+ }
+ return rows;
+ });
+}
+
+/**
+ * Inserts one row into a table keyed by something other than `id`, reading it
+ * back by an explicit condition.
+ *
+ * `user_preferences` is keyed by `userId` and has no `id` column at all, so
+ * there is no insertId to read back by — the caller has to say what identifies
+ * the row it just wrote.
+ */
+export async function insertReturningWhere(
+ context: DatabaseContext,
+ table: T,
+ values: T["$inferInsert"],
+ where: SQL,
+): Promise {
+ const db = context.drizzle;
+
+ if (supportsReturning(context.dialect)) {
+ return db.insert(table).values(values).returning() as Promise<
+ T["$inferSelect"][]
+ >;
+ }
+
+ return db.transaction(async (tx) => {
+ await tx.insert(table).values(values);
+ const rows = await tx.select().from(table).where(where);
+ if (rows.length === 0) {
+ throw new Error(
+ `Inserted into ${String(table)} but the read-back condition matched nothing.`,
+ );
+ }
+ return rows;
+ });
+}
+
+/**
+ * Insert, or update the row that collides with it.
+ *
+ * The clause has three spellings. SQLite and Postgres take
+ * `ON CONFLICT (cols) DO UPDATE`; **MySQL takes `ON DUPLICATE KEY UPDATE` and
+ * names no columns** — it uses whichever unique key was violated. drizzle
+ * follows suit, so `onConflictDoUpdate` does not exist on mysql-core at all and
+ * calling it is a TypeError rather than a rejected query.
+ *
+ * The conflict target still has to be passed: it is what SQLite and Postgres
+ * need, and stating it keeps the caller honest about which unique constraint it
+ * is relying on — four of those were missing from the schema entirely until the
+ * cross-dialect tests went looking.
+ */
+export async function upsert(
+ context: DatabaseContext,
+ table: T,
+ values: T["$inferInsert"],
+ conflict: { target: SQLiteColumn[]; set: UpdateValues },
+): Promise {
+ const db = context.drizzle;
+
+ if (context.dialect === "mysql") {
+ const insert = db.insert(table).values(values) as unknown as {
+ onDuplicateKeyUpdate: (config: { set: UpdateValues }) => Promise;
+ };
+ await insert.onDuplicateKeyUpdate({ set: conflict.set });
+ return;
+ }
+
+ await db
+ .insert(table)
+ .values(values)
+ .onConflictDoUpdate({ target: conflict.target, set: conflict.set });
+}
diff --git a/src/backend/database/repositories/role-repository.ts b/src/backend/database/repositories/role-repository.ts
index 47f53d76..3790f238 100644
--- a/src/backend/database/repositories/role-repository.ts
+++ b/src/backend/database/repositories/role-repository.ts
@@ -1,6 +1,8 @@
import { and, eq, inArray } from "drizzle-orm";
import { hostAccess, roles, userRoles } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { deleteReturning, insertReturning } from "./returning.js";
export type RoleRecord = typeof roles.$inferSelect;
export type NewRoleRecord = typeof roles.$inferInsert;
@@ -62,27 +64,27 @@ export class RoleRepository {
}
async createRole(role: NewRoleRecord): Promise {
- const result = await this.context.drizzle.insert(roles).values(role);
+ const [created] = await insertReturning(this.context, roles, role);
await this.afterWrite();
- return Number(result.lastInsertRowid);
+ return created.id;
}
async updateRole(id: number, update: RoleUpdate): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(roles)
.set(update)
- .where(eq(roles.id, id))
- .returning({ id: roles.id });
+ .where(eq(roles.id, id));
await this.afterWrite();
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteRole(id: number): Promise<{ deletedUserIds: string[] }> {
- const deletedUserRoles = await this.context.drizzle
- .delete(userRoles)
- .where(eq(userRoles.roleId, id))
- .returning({ userId: userRoles.userId });
+ const deletedUserRoles = await deleteReturning(
+ this.context,
+ userRoles,
+ eq(userRoles.roleId, id),
+ );
await this.context.drizzle
.delete(hostAccess)
@@ -169,16 +171,15 @@ export class RoleRepository {
}
if (removeRole) {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(userRoles)
.where(
and(
eq(userRoles.userId, input.userId),
eq(userRoles.roleId, removeRole.id),
),
- )
- .returning({ id: userRoles.id });
- removed = rows.length > 0;
+ );
+ removed = rowsAffected(result) > 0;
}
if (added || removed) {
@@ -196,16 +197,15 @@ export class RoleRepository {
}
async removeAllRolesFromUser(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(userRoles)
- .where(eq(userRoles.userId, userId))
- .returning({ id: userRoles.id });
+ .where(eq(userRoles.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async listUserRoleIds(userId: string): Promise {
diff --git a/src/backend/database/repositories/session-recording-repository.ts b/src/backend/database/repositories/session-recording-repository.ts
index fa3efc10..7961e473 100644
--- a/src/backend/database/repositories/session-recording-repository.ts
+++ b/src/backend/database/repositories/session-recording-repository.ts
@@ -1,6 +1,8 @@
import { and, desc, eq, inArray, lt } from "drizzle-orm";
import { hosts, sessionRecordings } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect;
@@ -47,10 +49,11 @@ export class SessionRecordingRepository {
async create(
input: SessionRecordingCreateInput,
): Promise {
- const [created] = await this.context.drizzle
- .insert(sessionRecordings)
- .values(input)
- .returning();
+ const [created] = await insertReturning(
+ this.context,
+ sessionRecordings,
+ input,
+ );
await this.afterWrite();
return created;
@@ -170,57 +173,71 @@ export class SessionRecordingRepository {
}
async deleteById(id: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionRecordings)
- .where(eq(sessionRecordings.id, id))
- .returning({ id: sessionRecordings.id });
+ .where(eq(sessionRecordings.id, id));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteForUser(userId: string, id: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionRecordings)
.where(
and(eq(sessionRecordings.id, id), eq(sessionRecordings.userId, userId)),
- )
- .returning({ id: sessionRecordings.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
+ }
+
+ /**
+ * Detaches recordings from a user being deleted instead of removing them.
+ * A recording is evidence about the host as much as about the person, and the
+ * file stays on disk regardless — deleting only the row would orphan it.
+ */
+ async anonymizeByUserId(userId: string): Promise {
+ const result = await this.context.drizzle
+ .update(sessionRecordings)
+ .set({ userId: null })
+ .where(eq(sessionRecordings.userId, userId));
+
+ if (rowsAffected(result) > 0) {
+ await this.afterWrite();
+ }
+
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionRecordings)
- .where(eq(sessionRecordings.userId, userId))
- .returning({ id: sessionRecordings.id });
+ .where(eq(sessionRecordings.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionRecordings)
- .where(eq(sessionRecordings.hostId, hostId))
- .returning({ id: sessionRecordings.id });
+ .where(eq(sessionRecordings.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostIds(hostIds: number[]): Promise {
@@ -228,16 +245,15 @@ export class SessionRecordingRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionRecordings)
- .where(inArray(sessionRecordings.hostId, hostIds))
- .returning({ id: sessionRecordings.id });
+ .where(inArray(sessionRecordings.hostId, hostIds));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/session-repository.ts b/src/backend/database/repositories/session-repository.ts
index ff83c46e..6379a214 100644
--- a/src/backend/database/repositories/session-repository.ts
+++ b/src/backend/database/repositories/session-repository.ts
@@ -1,6 +1,8 @@
import { and, eq, lte, ne } from "drizzle-orm";
import { sessions } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type SessionRecord = typeof sessions.$inferSelect;
export type NewSessionRecord = typeof sessions.$inferInsert;
@@ -12,10 +14,7 @@ export class SessionRepository {
) {}
async create(session: NewSessionRecord): Promise {
- const rows = await this.context.drizzle
- .insert(sessions)
- .values(session)
- .returning();
+ const rows = await insertReturning(this.context, sessions, session);
await this.afterWrite();
return rows[0];
}
@@ -72,13 +71,12 @@ export class SessionRepository {
}
async revoke(id: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessions)
- .where(eq(sessions.id, id))
- .returning({ id: sessions.id });
+ .where(eq(sessions.id, id));
await this.afterWrite();
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async revokeAllForUser(
@@ -89,23 +87,19 @@ export class SessionRepository {
? and(eq(sessions.userId, userId), ne(sessions.id, exceptSessionId))
: eq(sessions.userId, userId);
- const rows = await this.context.drizzle
- .delete(sessions)
- .where(where)
- .returning({ id: sessions.id });
+ const result = await this.context.drizzle.delete(sessions).where(where);
await this.afterWrite();
- return rows.length;
+ return rowsAffected(result);
}
async deleteExpired(now = new Date()): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessions)
- .where(lte(sessions.expiresAt, now.toISOString()))
- .returning({ id: sessions.id });
+ .where(lte(sessions.expiresAt, now.toISOString()));
await this.afterWrite();
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/session-share-repository.ts b/src/backend/database/repositories/session-share-repository.ts
index 016c44f1..39a8495b 100644
--- a/src/backend/database/repositories/session-share-repository.ts
+++ b/src/backend/database/repositories/session-share-repository.ts
@@ -6,6 +6,8 @@ import {
users,
} from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type SessionShareRecord = typeof sessionShares.$inferSelect;
export type SessionShareParticipantRecord =
@@ -49,22 +51,19 @@ export class SessionShareRepository {
) {}
async create(input: SessionShareCreateInput): Promise {
- const [created] = await this.context.drizzle
- .insert(sessionShares)
- .values({
- id: input.id,
- hostId: input.hostId,
- ownerUserId: input.ownerUserId,
- protocol: input.protocol,
- sessionId: input.sessionId,
- tabInstanceId: input.tabInstanceId ?? null,
- shareType: input.shareType,
- targetUserId: input.targetUserId ?? null,
- linkToken: input.linkToken ?? null,
- permissionLevel: input.permissionLevel,
- expiresAt: input.expiresAt,
- })
- .returning();
+ const [created] = await insertReturning(this.context, sessionShares, {
+ id: input.id,
+ hostId: input.hostId,
+ ownerUserId: input.ownerUserId,
+ protocol: input.protocol,
+ sessionId: input.sessionId,
+ tabInstanceId: input.tabInstanceId ?? null,
+ shareType: input.shareType,
+ targetUserId: input.targetUserId ?? null,
+ linkToken: input.linkToken ?? null,
+ permissionLevel: input.permissionLevel,
+ expiresAt: input.expiresAt,
+ });
await this.afterWrite();
return created;
@@ -151,7 +150,7 @@ export class SessionShareRepository {
}
async revoke(shareId: string, requestingUserId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(sessionShares)
.set({ revokedAt: new Date().toISOString() })
.where(
@@ -159,38 +158,35 @@ export class SessionShareRepository {
eq(sessionShares.id, shareId),
eq(sessionShares.ownerUserId, requestingUserId),
),
- )
- .returning({ id: sessionShares.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async revokeAsAdmin(shareId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(sessionShares)
.set({ revokedAt: new Date().toISOString() })
- .where(eq(sessionShares.id, shareId))
- .returning({ id: sessionShares.id });
+ .where(eq(sessionShares.id, shareId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteExpiredShares(now = new Date().toISOString()): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionShares)
- .where(lt(sessionShares.expiresAt, now))
- .returning({ id: sessionShares.id });
+ .where(lt(sessionShares.expiresAt, now));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async touchShareUsage(
@@ -213,10 +209,11 @@ export class SessionShareRepository {
userId: string | null,
guestLabel: string | null,
): Promise {
- const [created] = await this.context.drizzle
- .insert(sessionShareParticipants)
- .values({ shareId, userId, guestLabel })
- .returning();
+ const [created] = await insertReturning(
+ this.context,
+ sessionShareParticipants,
+ { shareId, userId, guestLabel },
+ );
await this.afterWrite();
return created;
}
@@ -230,15 +227,14 @@ export class SessionShareRepository {
}
async deleteSharesForHost(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sessionShares)
- .where(eq(sessionShares.hostId, hostId))
- .returning({ id: sessionShares.id });
+ .where(eq(sessionShares.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/settings-cache.ts b/src/backend/database/repositories/settings-cache.ts
new file mode 100644
index 00000000..a8b43f81
--- /dev/null
+++ b/src/backend/database/repositories/settings-cache.ts
@@ -0,0 +1,52 @@
+/**
+ * Synchronous read-through cache for the settings table.
+ *
+ * 27 call sites read settings synchronously — during startup, inside request
+ * handlers, and from the guacd server bootstrap. On SQLite that works because
+ * better-sqlite3 is synchronous; on Postgres or MySQL there is no synchronous
+ * query at all, and making all 27 async would push `await` through code paths
+ * that have no business being asynchronous.
+ *
+ * Settings are a handful of low-cardinality configuration rows that change
+ * rarely and are read constantly, so they are cached in full. Writes go through
+ * SettingsRepository, which updates the cache in the same call, and the cache is
+ * primed once at startup.
+ */
+
+let cache: Map | null = null;
+
+export function isSettingsCachePrimed(): boolean {
+ return cache !== null;
+}
+
+/** Loads the full settings table. Called once during startup. */
+export function primeSettingsCache(
+ rows: { key: string; value: string }[],
+): void {
+ cache = new Map(rows.map((row) => [row.key, row.value]));
+}
+
+/**
+ * Reads a cached setting.
+ *
+ * Returns null both for "not set" and "cache not primed yet" — every caller
+ * already treats a missing setting as "use the default", and startup ordering
+ * means a read before priming should behave the same way rather than throw.
+ */
+export function readCachedSetting(key: string): string | null {
+ return cache?.get(key) ?? null;
+}
+
+/** Keeps the cache in step with a write. */
+export function updateCachedSetting(key: string, value: string): void {
+ cache?.set(key, value);
+}
+
+export function forgetCachedSetting(key: string): void {
+ cache?.delete(key);
+}
+
+/** Test seam. */
+export function resetSettingsCache(): void {
+ cache = null;
+}
diff --git a/src/backend/database/repositories/settings-repository.ts b/src/backend/database/repositories/settings-repository.ts
index 3b69fbbc..ac043aab 100644
--- a/src/backend/database/repositories/settings-repository.ts
+++ b/src/backend/database/repositories/settings-repository.ts
@@ -1,6 +1,8 @@
import { eq, like } from "drizzle-orm";
import { settings } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { forgetCachedSetting, updateCachedSetting } from "./settings-cache.js";
+import { deleteReturning } from "./returning.js";
export class SettingsRepository {
constructor(
@@ -34,6 +36,9 @@ export class SettingsRepository {
const existing = await this.get(key);
if (existing === null) {
await this.context.drizzle.insert(settings).values({ key, value });
+ // Kept in step here so the synchronous readers cannot observe a stale
+ // value after a write in the same process.
+ updateCachedSetting(key, value);
await this.afterWrite();
return;
}
@@ -42,6 +47,7 @@ export class SettingsRepository {
.update(settings)
.set({ value })
.where(eq(settings.key, key));
+ updateCachedSetting(key, value);
await this.afterWrite();
}
@@ -51,14 +57,17 @@ export class SettingsRepository {
async delete(key: string): Promise {
await this.context.drizzle.delete(settings).where(eq(settings.key, key));
+ forgetCachedSetting(key);
await this.afterWrite();
}
async deleteLike(pattern: string): Promise {
- const rows = await this.context.drizzle
- .delete(settings)
- .where(like(settings.key, pattern))
- .returning({ key: settings.key });
+ const rows = await deleteReturning(
+ this.context,
+ settings,
+ like(settings.key, pattern),
+ );
+ for (const row of rows) forgetCachedSetting(row.key);
await this.afterWrite();
return rows.length;
}
diff --git a/src/backend/database/repositories/shared-host-auth-override-repository.ts b/src/backend/database/repositories/shared-host-auth-override-repository.ts
new file mode 100644
index 00000000..653c50fb
--- /dev/null
+++ b/src/backend/database/repositories/shared-host-auth-override-repository.ts
@@ -0,0 +1,102 @@
+import { and, eq } from "drizzle-orm";
+import type { AuthOverrideProtocol } from "../../../types/auth-protocols.js";
+import { sharedHostAuthOverrides } from "../db/schema.js";
+import type { DatabaseContext } from "./database-context.js";
+import { deleteReturning, upsert } from "./returning.js";
+
+export type SharedHostAuthOverrideRecord =
+ typeof sharedHostAuthOverrides.$inferSelect;
+
+export class SharedHostAuthOverrideRepository {
+ constructor(
+ private readonly context: DatabaseContext,
+ private readonly onWrite?: () => void | Promise,
+ ) {}
+
+ async findForHostUser(
+ hostId: number,
+ userId: string,
+ protocol: AuthOverrideProtocol,
+ ): Promise {
+ const rows = await this.context.drizzle
+ .select()
+ .from(sharedHostAuthOverrides)
+ .where(
+ and(
+ eq(sharedHostAuthOverrides.hostId, hostId),
+ eq(sharedHostAuthOverrides.userId, userId),
+ eq(sharedHostAuthOverrides.protocol, protocol),
+ ),
+ )
+ .limit(1);
+
+ return rows[0] ?? null;
+ }
+
+ async findCredentialId(
+ hostId: number,
+ userId: string,
+ protocol: AuthOverrideProtocol,
+ ): Promise {
+ return (
+ (await this.findForHostUser(hostId, userId, protocol))?.credentialId ??
+ null
+ );
+ }
+
+ async setCredential(
+ hostId: number,
+ userId: string,
+ protocol: AuthOverrideProtocol,
+ credentialId: number,
+ ): Promise {
+ await upsert(
+ this.context,
+ sharedHostAuthOverrides,
+ {
+ hostId,
+ userId,
+ protocol,
+ credentialId,
+ },
+ {
+ target: [
+ sharedHostAuthOverrides.hostId,
+ sharedHostAuthOverrides.userId,
+ sharedHostAuthOverrides.protocol,
+ ],
+ set: {
+ credentialId,
+ updatedAt: new Date().toISOString(),
+ },
+ },
+ );
+
+ await this.afterWrite();
+ }
+
+ async clearCredential(
+ hostId: number,
+ userId: string,
+ protocol: AuthOverrideProtocol,
+ ): Promise {
+ const rows = await deleteReturning(
+ this.context,
+ sharedHostAuthOverrides,
+ and(
+ eq(sharedHostAuthOverrides.hostId, hostId),
+ eq(sharedHostAuthOverrides.userId, userId),
+ eq(sharedHostAuthOverrides.protocol, protocol),
+ ),
+ );
+
+ if (rows.length > 0) {
+ await this.afterWrite();
+ }
+ return rows.length > 0;
+ }
+
+ private async afterWrite(): Promise {
+ await this.onWrite?.();
+ }
+}
diff --git a/src/backend/database/repositories/shared-host-secrets-repository.ts b/src/backend/database/repositories/shared-host-secrets-repository.ts
index 23e2a86d..56cd2249 100644
--- a/src/backend/database/repositories/shared-host-secrets-repository.ts
+++ b/src/backend/database/repositories/shared-host-secrets-repository.ts
@@ -1,6 +1,7 @@
import { and, eq, inArray, or } from "drizzle-orm";
import { hostAccess, hosts, sharedHostSecrets } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type SharedHostSecretRecord = typeof sharedHostSecrets.$inferSelect;
export type NewSharedHostSecretRecord = typeof sharedHostSecrets.$inferInsert;
@@ -108,16 +109,15 @@ export class SharedHostSecretsRepository {
}
async deleteByHostAccessId(hostAccessId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sharedHostSecrets)
- .where(eq(sharedHostSecrets.hostAccessId, hostAccessId))
- .returning({ id: sharedHostSecrets.id });
+ .where(eq(sharedHostSecrets.hostAccessId, hostAccessId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteForRoleMember(
@@ -148,29 +148,27 @@ export class SharedHostSecretsRepository {
}
async deleteByOriginalCredentialId(credentialId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sharedHostSecrets)
- .where(eq(sharedHostSecrets.originalCredentialId, credentialId))
- .returning({ id: sharedHostSecrets.id });
+ .where(eq(sharedHostSecrets.originalCredentialId, credentialId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByTargetUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sharedHostSecrets)
- .where(eq(sharedHostSecrets.targetUserId, userId))
- .returning({ id: sharedHostSecrets.id });
+ .where(eq(sharedHostSecrets.targetUserId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async findHostIdsReferencingCredential(
diff --git a/src/backend/database/repositories/snippet-repository.ts b/src/backend/database/repositories/snippet-repository.ts
index 60e6bf17..8b511c3c 100644
--- a/src/backend/database/repositories/snippet-repository.ts
+++ b/src/backend/database/repositories/snippet-repository.ts
@@ -2,6 +2,12 @@ import { and, asc, eq, sql } from "drizzle-orm";
import { randomUUID } from "crypto";
import { snippetFolders, snippets } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type SnippetRecord = typeof snippets.$inferSelect;
export type SnippetFolderRecord = typeof snippetFolders.$inferSelect;
@@ -84,11 +90,16 @@ export class SnippetRepository {
}
async listSnippetsForExport(userId: string): Promise {
- return this.context.drizzle
- .select()
- .from(snippets)
- .where(eq(snippets.userId, userId))
- .orderBy(asc(snippets.folder), asc(snippets.order));
+ return (
+ this.context.drizzle
+ .select()
+ .from(snippets)
+ .where(eq(snippets.userId, userId))
+ // coalesce, not asc(folder): folder is nullable, and NULLs sort first on
+ // SQLite and MySQL but last on Postgres. An export whose row order depends
+ // on the engine is not much of an export.
+ .orderBy(sql`coalesce(${snippets.folder}, '')`, asc(snippets.order))
+ );
}
async listFoldersForExport(userId: string): Promise {
@@ -149,19 +160,16 @@ export class SnippetRepository {
? await this.nextOrderForFolder(userId, folderValue)
: input.order;
- const rows = await this.context.drizzle
- .insert(snippets)
- .values({
- syncId: randomUUID(),
- userId,
- name: input.name.trim(),
- content: input.content.trim(),
- description: input.description?.trim() || null,
- folder: input.folder?.trim() || null,
- order,
- hostFilter: input.hostFilter ? JSON.stringify(input.hostFilter) : null,
- })
- .returning();
+ const rows = await insertReturning(this.context, snippets, {
+ syncId: randomUUID(),
+ userId,
+ name: input.name.trim(),
+ content: input.content.trim(),
+ description: input.description?.trim() || null,
+ folder: input.folder?.trim() || null,
+ order,
+ hostFilter: input.hostFilter ? JSON.stringify(input.hostFilter) : null,
+ });
await this.afterWrite();
return rows[0];
@@ -200,11 +208,12 @@ export class SnippetRepository {
? JSON.stringify(input.hostFilter)
: null;
- const rows = await this.context.drizzle
- .update(snippets)
- .set(updateFields)
- .where(and(eq(snippets.id, snippetId), eq(snippets.userId, userId)))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ snippets,
+ updateFields,
+ and(eq(snippets.id, snippetId), eq(snippets.userId, userId)),
+ );
await this.afterWrite();
return { existing, updated: rows[0] };
@@ -229,23 +238,21 @@ export class SnippetRepository {
snippetsDeleted: number;
foldersDeleted: number;
}> {
- const deletedSnippets = await this.context.drizzle
+ const snippetResult = await this.context.drizzle
.delete(snippets)
- .where(eq(snippets.userId, userId))
- .returning({ id: snippets.id });
+ .where(eq(snippets.userId, userId));
- const deletedFolders = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(snippetFolders)
- .where(eq(snippetFolders.userId, userId))
- .returning({ id: snippetFolders.id });
+ .where(eq(snippetFolders.userId, userId));
- if (deletedSnippets.length > 0 || deletedFolders.length > 0) {
+ if (rowsAffected(snippetResult) > 0 || rowsAffected(result) > 0) {
await this.afterWrite();
}
return {
- snippetsDeleted: deletedSnippets.length,
- foldersDeleted: deletedFolders.length,
+ snippetsDeleted: rowsAffected(snippetResult),
+ foldersDeleted: rowsAffected(result),
};
}
@@ -377,16 +384,13 @@ export class SnippetRepository {
const existing = await this.findFolderByName(userId, name);
if (existing) return null;
- const rows = await this.context.drizzle
- .insert(snippetFolders)
- .values({
- syncId: randomUUID(),
- userId,
- name: name.trim(),
- color: color?.trim() || null,
- icon: icon?.trim() || null,
- })
- .returning();
+ const rows = await insertReturning(this.context, snippetFolders, {
+ syncId: randomUUID(),
+ userId,
+ name: name.trim(),
+ color: color?.trim() || null,
+ icon: icon?.trim() || null,
+ });
if (triggerSave) {
await this.afterWrite();
@@ -414,13 +418,12 @@ export class SnippetRepository {
if (color !== undefined) updateFields.color = color?.trim() || null;
if (icon !== undefined) updateFields.icon = icon?.trim() || null;
- const rows = await this.context.drizzle
- .update(snippetFolders)
- .set(updateFields)
- .where(
- and(eq(snippetFolders.userId, userId), eq(snippetFolders.name, name)),
- )
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ snippetFolders,
+ updateFields,
+ and(eq(snippetFolders.userId, userId), eq(snippetFolders.name, name)),
+ );
await this.afterWrite();
return rows[0] ?? null;
@@ -465,15 +468,14 @@ export class SnippetRepository {
.set({ folder: null })
.where(and(eq(snippets.userId, userId), eq(snippets.folder, name)));
- const rows = await this.context.drizzle
- .delete(snippetFolders)
- .where(
- and(eq(snippetFolders.userId, userId), eq(snippetFolders.name, name)),
- )
- .returning({ syncId: snippetFolders.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ snippetFolders,
+ and(eq(snippetFolders.userId, userId), eq(snippetFolders.name, name)),
+ );
await this.afterWrite();
- return rows[0] ?? null;
+ return rows[0] ? { syncId: rows[0].syncId } : null;
}
private async findFolderByName(
diff --git a/src/backend/database/repositories/sql-timestamp.ts b/src/backend/database/repositories/sql-timestamp.ts
new file mode 100644
index 00000000..3d41168e
--- /dev/null
+++ b/src/backend/database/repositories/sql-timestamp.ts
@@ -0,0 +1,20 @@
+/**
+ * Timestamp columns are stored as text defaulting to `CURRENT_TIMESTAMP`, which
+ * every supported engine writes as `YYYY-MM-DD HH:MM:SS` in UTC. That format
+ * sorts lexicographically in time order, so retention cutoffs can be plain
+ * string comparisons.
+ *
+ * Computing the cutoff here rather than with `datetime('now', ?)` keeps the
+ * queries free of engine-specific date functions.
+ */
+export function sqlTimestampDaysAgo(
+ days: number,
+ now: Date = new Date(),
+): string {
+ const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
+ return formatSqlTimestamp(cutoff);
+}
+
+export function formatSqlTimestamp(date: Date): string {
+ return date.toISOString().slice(0, 19).replace("T", " ");
+}
diff --git a/src/backend/database/repositories/sqlite-foreign-keys.ts b/src/backend/database/repositories/sqlite-foreign-keys.ts
index 09abf115..8b55d755 100644
--- a/src/backend/database/repositories/sqlite-foreign-keys.ts
+++ b/src/backend/database/repositories/sqlite-foreign-keys.ts
@@ -1,4 +1,5 @@
import { getCurrentRepositorySqlite } from "./factory.js";
+import { needsExplicitPersist, resolveDatabaseDialect } from "../db/dialect.js";
export interface SqliteForeignKeyClient {
exec(sql: string): unknown;
@@ -16,8 +17,28 @@ export async function withSqliteForeignKeysDisabled(
}
}
+/**
+ * Runs a bulk import with foreign keys relaxed.
+ *
+ * Backup restore writes tables in an order that is not dependency-safe, so the
+ * constraints have to stand down for the duration.
+ *
+ * **This has no equivalent on Postgres or MySQL here.** Postgres needs
+ * superuser to disable triggers, and MySQL's `SET FOREIGN_KEY_CHECKS = 0` is
+ * per-connection, which a pool does not guarantee. Rather than run the import
+ * with constraints enforced and have it fail partway through — leaving a
+ * half-restored database — it refuses with a message that says why.
+ */
export async function withCurrentSqliteForeignKeysDisabled(
operation: () => Promise,
): Promise {
+ const dialect = resolveDatabaseDialect();
+ if (!needsExplicitPersist(dialect)) {
+ throw new Error(
+ `Importing a backup is only supported on SQLite; this deployment uses ${dialect}. ` +
+ `Restore into the database directly with its own tooling instead.`,
+ );
+ }
+
return withSqliteForeignKeysDisabled(getCurrentRepositorySqlite(), operation);
}
diff --git a/src/backend/database/repositories/ssh-credential-usage-repository.ts b/src/backend/database/repositories/ssh-credential-usage-repository.ts
index 97d1aa0e..4558d64a 100644
--- a/src/backend/database/repositories/ssh-credential-usage-repository.ts
+++ b/src/backend/database/repositories/ssh-credential-usage-repository.ts
@@ -1,6 +1,8 @@
import { eq, inArray } from "drizzle-orm";
import { sshCredentialUsage } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type SshCredentialUsageRecord = typeof sshCredentialUsage.$inferSelect;
@@ -22,38 +24,37 @@ export class SshCredentialUsageRepository {
hostId: number,
userId: string,
): Promise {
- const [created] = await this.context.drizzle
- .insert(sshCredentialUsage)
- .values({ credentialId, hostId, userId })
- .returning();
+ const [created] = await insertReturning(this.context, sshCredentialUsage, {
+ credentialId,
+ hostId,
+ userId,
+ });
await this.afterWrite();
return created;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sshCredentialUsage)
- .where(eq(sshCredentialUsage.userId, userId))
- .returning({ id: sshCredentialUsage.id });
+ .where(eq(sshCredentialUsage.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sshCredentialUsage)
- .where(eq(sshCredentialUsage.hostId, hostId))
- .returning({ id: sshCredentialUsage.id });
+ .where(eq(sshCredentialUsage.hostId, hostId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostIds(hostIds: number[]): Promise {
@@ -61,16 +62,15 @@ export class SshCredentialUsageRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(sshCredentialUsage)
- .where(inArray(sshCredentialUsage.hostId, hostIds))
- .returning({ id: sshCredentialUsage.id });
+ .where(inArray(sshCredentialUsage.hostId, hostIds));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/sso-provider-repository.ts b/src/backend/database/repositories/sso-provider-repository.ts
index 403a81a0..275e3981 100644
--- a/src/backend/database/repositories/sso-provider-repository.ts
+++ b/src/backend/database/repositories/sso-provider-repository.ts
@@ -1,6 +1,8 @@
import { asc, eq } from "drizzle-orm";
import { ssoProviders, users } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type SsoProviderRecord = typeof ssoProviders.$inferSelect;
export type NewSsoProviderRecord = typeof ssoProviders.$inferInsert;
@@ -76,10 +78,7 @@ export class SsoProviderRepository {
}
async create(provider: NewSsoProviderRecord): Promise {
- const rows = await this.context.drizzle
- .insert(ssoProviders)
- .values(provider)
- .returning();
+ const rows = await insertReturning(this.context, ssoProviders, provider);
await this.afterWrite();
return rows[0];
@@ -89,27 +88,27 @@ export class SsoProviderRepository {
id: number,
update: SsoProviderUpdate,
): Promise {
- const rows = await this.context.drizzle
- .update(ssoProviders)
- .set(update)
- .where(eq(ssoProviders.id, id))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ ssoProviders,
+ update,
+ eq(ssoProviders.id, id),
+ );
await this.afterWrite();
return rows[0] ?? null;
}
async delete(id: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(ssoProviders)
- .where(eq(ssoProviders.id, id))
- .returning({ id: ssoProviders.id });
+ .where(eq(ssoProviders.id, id));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async countUsersByProviderId(providerId: number): Promise {
diff --git a/src/backend/database/repositories/sync-tombstone-repository.ts b/src/backend/database/repositories/sync-tombstone-repository.ts
index 1fe4953f..a0378aae 100644
--- a/src/backend/database/repositories/sync-tombstone-repository.ts
+++ b/src/backend/database/repositories/sync-tombstone-repository.ts
@@ -1,5 +1,6 @@
-import { and, eq, gt } from "drizzle-orm";
+import { and, eq } from "drizzle-orm";
import { syncTombstones } from "../db/schema.js";
+import { timestampAtOrAfter } from "../sync-timestamp.js";
import type { DatabaseContext } from "./database-context.js";
export type SyncTombstoneRecord = typeof syncTombstones.$inferSelect;
@@ -12,7 +13,8 @@ export type SyncEntityType =
| "snippetFolders"
| "vaultProfiles"
| "dashboardServiceLinks"
- | "homepageItems";
+ | "homepageItems"
+ | "userPreferences";
export class SyncTombstoneRepository {
constructor(
@@ -56,7 +58,8 @@ export class SyncTombstoneRepository {
eq(syncTombstones.userId, userId),
eq(syncTombstones.entityType, entityType),
];
- if (since) conditions.push(gt(syncTombstones.deletedAt, since));
+ if (since)
+ conditions.push(timestampAtOrAfter(syncTombstones.deletedAt, since));
return this.context.drizzle
.select()
diff --git a/src/backend/database/repositories/termix-identity-ca-repository.ts b/src/backend/database/repositories/termix-identity-ca-repository.ts
index ea1fcd3b..c95e9cef 100644
--- a/src/backend/database/repositories/termix-identity-ca-repository.ts
+++ b/src/backend/database/repositories/termix-identity-ca-repository.ts
@@ -2,6 +2,12 @@ import { eq } from "drizzle-orm";
import { termixIdentityCa } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
import { DataCrypto } from "../../utils/data-crypto.js";
+import {
+ insertedId,
+ rowsAffected,
+ supportsReturning,
+} from "./mutation-result.js";
+import { updateReturning } from "./returning.js";
export type TermixIdentityCaRecord = typeof termixIdentityCa.$inferSelect;
export type NewTermixIdentityCaRecord = typeof termixIdentityCa.$inferInsert;
@@ -54,27 +60,7 @@ export class TermixIdentityCaRepository {
ca: NewTermixIdentityCaRecord,
): Promise {
const userDataKey = DataCrypto.validateUserAccess(userId);
- const result = this.context.drizzle.transaction((tx) => {
- const inserted = tx
- .insert(termixIdentityCa)
- .values({ ...ca, privateKey: "" })
- .returning()
- .all();
- const row = inserted[0];
- const encrypted = DataCrypto.encryptRecord(
- "termix_identity_ca",
- { id: row.id, privateKey: ca.privateKey },
- userId,
- userDataKey,
- );
-
- return tx
- .update(termixIdentityCa)
- .set({ privateKey: encrypted.privateKey })
- .where(eq(termixIdentityCa.id, row.id))
- .returning()
- .all()[0];
- });
+ const result = await this.insertThenEncrypt(userId, ca, userDataKey);
await this.afterWrite();
return DataCrypto.decryptRecord(
@@ -85,6 +71,81 @@ export class TermixIdentityCaRepository {
);
}
+ /**
+ * Writes a CA in two steps, because the ciphertext depends on the id.
+ *
+ * The private key is encrypted with the row's own id as context, which does
+ * not exist until the row does. So: insert with an empty key, encrypt, update.
+ * The empty key must never be observable, hence the transaction.
+ *
+ * Two branches because better-sqlite3 rejects an async transaction callback —
+ * see the same note in UserRepository.
+ */
+ private async insertThenEncrypt(
+ userId: string,
+ ca: NewTermixIdentityCaRecord,
+ userDataKey: Buffer,
+ ): Promise {
+ const draft = { ...ca, privateKey: "" };
+
+ const seal = (id: number) =>
+ DataCrypto.encryptRecord(
+ "termix_identity_ca",
+ { id, privateKey: ca.privateKey },
+ userId,
+ userDataKey,
+ ).privateKey;
+
+ if (this.context.dialect === "sqlite") {
+ /* eslint-disable no-restricted-syntax -- sqlite-only branch: the dialect
+ is checked directly above, and better-sqlite3 needs the synchronous
+ .all() form, which has no async equivalent. */
+ return this.context.drizzle.transaction((tx) => {
+ const row = tx
+ .insert(termixIdentityCa)
+ .values(draft)
+ .returning()
+ .all()[0];
+ return tx
+ .update(termixIdentityCa)
+ .set({ privateKey: seal(row.id) })
+ .where(eq(termixIdentityCa.id, row.id))
+ .returning()
+ .all()[0];
+ });
+ /* eslint-enable no-restricted-syntax */
+ }
+
+ return this.context.drizzle.transaction(async (tx) => {
+ let id: number | null;
+ if (supportsReturning(this.context.dialect)) {
+ // eslint-disable-next-line no-restricted-syntax -- guarded by the check above
+ const rows = await tx
+ .insert(termixIdentityCa)
+ .values(draft)
+ .returning();
+ id = rows[0]?.id ?? null;
+ } else {
+ id = insertedId(await tx.insert(termixIdentityCa).values(draft));
+ }
+
+ if (id === null) {
+ throw new Error("Insert into termix_identity_ca returned no id.");
+ }
+
+ await tx
+ .update(termixIdentityCa)
+ .set({ privateKey: seal(id) })
+ .where(eq(termixIdentityCa.id, id));
+
+ const [row] = await tx
+ .select()
+ .from(termixIdentityCa)
+ .where(eq(termixIdentityCa.id, id));
+ return row;
+ });
+ }
+
async updateEncryptedForIdentity(
userId: string,
identityId: number,
@@ -103,43 +164,42 @@ export class TermixIdentityCaRepository {
).privateKey
: undefined;
- const rows = await this.context.drizzle
- .update(termixIdentityCa)
- .set({
+ const rows = await updateReturning(
+ this.context,
+ termixIdentityCa,
+ {
...update,
...(encryptedPrivateKey ? { privateKey: encryptedPrivateKey } : {}),
- })
- .where(eq(termixIdentityCa.identityId, identityId))
- .returning();
+ },
+ eq(termixIdentityCa.identityId, identityId),
+ );
await this.afterWrite();
return this.decryptOne(rows[0] ?? null, userId);
}
async deleteByIdentityId(identityId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(termixIdentityCa)
- .where(eq(termixIdentityCa.identityId, identityId))
- .returning({ id: termixIdentityCa.id });
+ .where(eq(termixIdentityCa.identityId, identityId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(termixIdentityCa)
- .where(eq(termixIdentityCa.userId, userId))
- .returning({ id: termixIdentityCa.id });
+ .where(eq(termixIdentityCa.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private decryptOne>(
diff --git a/src/backend/database/repositories/termix-identity-repository.ts b/src/backend/database/repositories/termix-identity-repository.ts
index 9badeedd..eae585f8 100644
--- a/src/backend/database/repositories/termix-identity-repository.ts
+++ b/src/backend/database/repositories/termix-identity-repository.ts
@@ -1,6 +1,8 @@
import { and, asc, eq } from "drizzle-orm";
import { termixIdentities, termixIdentityKeys } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type TermixIdentityRecord = typeof termixIdentities.$inferSelect;
export type NewTermixIdentityRecord = typeof termixIdentities.$inferInsert;
@@ -57,10 +59,11 @@ export class TermixIdentityRepository {
async createIdentity(
identity: NewTermixIdentityRecord,
): Promise {
- const rows = await this.context.drizzle
- .insert(termixIdentities)
- .values(identity)
- .returning();
+ const rows = await insertReturning(
+ this.context,
+ termixIdentities,
+ identity,
+ );
await this.afterWrite();
return rows[0];
@@ -70,11 +73,12 @@ export class TermixIdentityRepository {
userId: string,
update: TermixIdentityUpdate,
): Promise {
- const rows = await this.context.drizzle
- .update(termixIdentities)
- .set(update)
- .where(eq(termixIdentities.userId, userId))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ termixIdentities,
+ update,
+ eq(termixIdentities.userId, userId),
+ );
if (rows.length > 0) {
await this.afterWrite();
@@ -84,39 +88,36 @@ export class TermixIdentityRepository {
}
async deleteIdentityForUser(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(termixIdentities)
- .where(eq(termixIdentities.userId, userId))
- .returning({ id: termixIdentities.id });
+ .where(eq(termixIdentities.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise<{
identitiesDeleted: number;
keysDeleted: number;
}> {
- const keyRows = await this.context.drizzle
+ const keyResult = await this.context.drizzle
.delete(termixIdentityKeys)
- .where(eq(termixIdentityKeys.userId, userId))
- .returning({ id: termixIdentityKeys.id });
+ .where(eq(termixIdentityKeys.userId, userId));
- const identityRows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(termixIdentities)
- .where(eq(termixIdentities.userId, userId))
- .returning({ id: termixIdentities.id });
+ .where(eq(termixIdentities.userId, userId));
- if (keyRows.length > 0 || identityRows.length > 0) {
+ if (rowsAffected(keyResult) > 0 || rowsAffected(result) > 0) {
await this.afterWrite();
}
return {
- identitiesDeleted: identityRows.length,
- keysDeleted: keyRows.length,
+ identitiesDeleted: rowsAffected(result),
+ keysDeleted: rowsAffected(keyResult),
};
}
@@ -170,10 +171,7 @@ export class TermixIdentityRepository {
async createKey(
key: NewTermixIdentityKeyRecord,
): Promise {
- const rows = await this.context.drizzle
- .insert(termixIdentityKeys)
- .values(key)
- .returning();
+ const rows = await insertReturning(this.context, termixIdentityKeys, key);
await this.afterWrite();
return rows[0];
@@ -184,16 +182,12 @@ export class TermixIdentityRepository {
id: number,
update: TermixIdentityKeyUpdate,
): Promise {
- const rows = await this.context.drizzle
- .update(termixIdentityKeys)
- .set(update)
- .where(
- and(
- eq(termixIdentityKeys.id, id),
- eq(termixIdentityKeys.userId, userId),
- ),
- )
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ termixIdentityKeys,
+ update,
+ and(eq(termixIdentityKeys.id, id), eq(termixIdentityKeys.userId, userId)),
+ );
if (rows.length > 0) {
await this.afterWrite();
@@ -203,21 +197,20 @@ export class TermixIdentityRepository {
}
async deleteKeyForUser(userId: string, id: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(termixIdentityKeys)
.where(
and(
eq(termixIdentityKeys.id, id),
eq(termixIdentityKeys.userId, userId),
),
- )
- .returning({ id: termixIdentityKeys.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async findKeyForUser(
diff --git a/src/backend/database/repositories/tmux-session-tag-repository.ts b/src/backend/database/repositories/tmux-session-tag-repository.ts
index 3d89d856..60735f0f 100644
--- a/src/backend/database/repositories/tmux-session-tag-repository.ts
+++ b/src/backend/database/repositories/tmux-session-tag-repository.ts
@@ -1,6 +1,7 @@
import { and, eq } from "drizzle-orm";
import { tmuxSessionTags } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type TmuxSessionTagRecord = typeof tmuxSessionTags.$inferSelect;
@@ -45,7 +46,7 @@ export class TmuxSessionTagRepository {
sessionName: string,
newSessionName: string,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(tmuxSessionTags)
.set({ sessionName: newSessionName })
.where(
@@ -53,35 +54,33 @@ export class TmuxSessionTagRepository {
eq(tmuxSessionTags.hostId, hostId),
eq(tmuxSessionTags.sessionName, sessionName),
),
- )
- .returning({ id: tmuxSessionTags.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteSessionForHost(
hostId: number,
sessionName: string,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(tmuxSessionTags)
.where(
and(
eq(tmuxSessionTags.hostId, hostId),
eq(tmuxSessionTags.sessionName, sessionName),
),
- )
- .returning({ id: tmuxSessionTags.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async replaceForUserHostSession(
@@ -90,7 +89,7 @@ export class TmuxSessionTagRepository {
sessionName: string,
tags: string[],
): Promise {
- const deletedRows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(tmuxSessionTags)
.where(
and(
@@ -98,8 +97,7 @@ export class TmuxSessionTagRepository {
eq(tmuxSessionTags.hostId, hostId),
eq(tmuxSessionTags.sessionName, sessionName),
),
- )
- .returning({ id: tmuxSessionTags.id });
+ );
if (tags.length > 0) {
await this.context.drizzle.insert(tmuxSessionTags).values(
@@ -112,7 +110,7 @@ export class TmuxSessionTagRepository {
);
}
- const changedRows = deletedRows.length + tags.length;
+ const changedRows = rowsAffected(result) + tags.length;
if (changedRows > 0) {
await this.afterWrite();
}
@@ -121,16 +119,15 @@ export class TmuxSessionTagRepository {
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(tmuxSessionTags)
- .where(eq(tmuxSessionTags.userId, userId))
- .returning({ id: tmuxSessionTags.id });
+ .where(eq(tmuxSessionTags.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/transfer-recent-repository.ts b/src/backend/database/repositories/transfer-recent-repository.ts
index 309b270e..de1266ea 100644
--- a/src/backend/database/repositories/transfer-recent-repository.ts
+++ b/src/backend/database/repositories/transfer-recent-repository.ts
@@ -1,6 +1,7 @@
import { and, desc, eq, inArray, or } from "drizzle-orm";
import { transferRecent } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
export type TransferRecentRecord = typeof transferRecent.$inferSelect;
@@ -100,47 +101,44 @@ export class TransferRecentRepository {
return 0;
}
- const deleted = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(transferRecent)
- .where(inArray(transferRecent.id, idsToDelete))
- .returning({ id: transferRecent.id });
+ .where(inArray(transferRecent.id, idsToDelete));
- if (deleted.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return deleted.length;
+ return rowsAffected(result);
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(transferRecent)
- .where(eq(transferRecent.userId, userId))
- .returning({ id: transferRecent.id });
+ .where(eq(transferRecent.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostId(hostId: number): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(transferRecent)
.where(
or(
eq(transferRecent.sourceHostId, hostId),
eq(transferRecent.destHostId, hostId),
),
- )
- .returning({ id: transferRecent.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
async deleteByHostIds(hostIds: number[]): Promise {
@@ -148,21 +146,20 @@ export class TransferRecentRepository {
return 0;
}
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(transferRecent)
.where(
or(
inArray(transferRecent.sourceHostId, hostIds),
inArray(transferRecent.destHostId, hostIds),
),
- )
- .returning({ id: transferRecent.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/user-preference-repository.ts b/src/backend/database/repositories/user-preference-repository.ts
index 7163c284..6a37a030 100644
--- a/src/backend/database/repositories/user-preference-repository.ts
+++ b/src/backend/database/repositories/user-preference-repository.ts
@@ -1,6 +1,8 @@
import { eq } from "drizzle-orm";
import { userPreferences } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturningWhere, updateReturning } from "./returning.js";
export type UserPreferenceRecord = typeof userPreferences.$inferSelect;
export type NewUserPreferenceRecord = typeof userPreferences.$inferInsert;
@@ -31,34 +33,36 @@ export class UserPreferenceRepository {
const existing = await this.findByUserId(userId);
if (!existing) {
- const rows = await this.context.drizzle
- .insert(userPreferences)
- .values({ userId, ...update })
- .returning();
+ const rows = await insertReturningWhere(
+ this.context,
+ userPreferences,
+ { userId, ...update },
+ eq(userPreferences.userId, userId),
+ );
await this.afterWrite();
return rows[0];
}
- const rows = await this.context.drizzle
- .update(userPreferences)
- .set(update)
- .where(eq(userPreferences.userId, userId))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ userPreferences,
+ update,
+ eq(userPreferences.userId, userId),
+ );
await this.afterWrite();
return rows[0];
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(userPreferences)
- .where(eq(userPreferences.userId, userId))
- .returning({ userId: userPreferences.userId });
+ .where(eq(userPreferences.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/user-repository.ts b/src/backend/database/repositories/user-repository.ts
index 68442f4f..59d6b75b 100644
--- a/src/backend/database/repositories/user-repository.ts
+++ b/src/backend/database/repositories/user-repository.ts
@@ -1,6 +1,8 @@
import { eq, inArray } from "drizzle-orm";
import { users } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected, supportsReturning } from "./mutation-result.js";
+import { insertReturning, updateReturning } from "./returning.js";
export type UserRecord = typeof users.$inferSelect;
export type NewUserRecord = typeof users.$inferInsert;
@@ -62,10 +64,7 @@ export class UserRepository {
}
async create(user: NewUserRecord): Promise {
- const rows = await this.context.drizzle
- .insert(users)
- .values(user)
- .returning();
+ const rows = await insertReturning(this.context, users, user);
await this.afterWrite();
return rows[0];
}
@@ -73,17 +72,10 @@ export class UserRepository {
async createFirstLocalUser(
user: NewFirstLocalUserRecord,
): Promise<{ user: UserRecord; isFirstUser: boolean }> {
- const result = this.context.drizzle.transaction((tx) => {
- const existingUsers = tx.select({ id: users.id }).from(users).all();
- const isFirstUser = existingUsers.length === 0;
- const rows = tx
- .insert(users)
- .values({ ...user, isAdmin: isFirstUser })
- .returning()
- .all();
-
- return { user: rows[0], isFirstUser };
- });
+ const result = await this.createCheckingIfFirst((isFirstUser) => ({
+ ...user,
+ isAdmin: isFirstUser,
+ }));
await this.afterWrite();
return result;
@@ -92,41 +84,87 @@ export class UserRepository {
async createFirstSsoUser(
user: NewUserRecord,
): Promise<{ user: UserRecord; isFirstUser: boolean }> {
- const result = this.context.drizzle.transaction((tx) => {
- const existingUsers = tx.select({ id: users.id }).from(users).all();
- const isFirstUser = existingUsers.length === 0;
- const rows = tx
- .insert(users)
- .values({ ...user, isAdmin: isFirstUser || Boolean(user.isAdmin) })
- .returning()
- .all();
-
- return { user: rows[0], isFirstUser };
- });
+ const result = await this.createCheckingIfFirst((isFirstUser) => ({
+ ...user,
+ isAdmin: isFirstUser || Boolean(user.isAdmin),
+ }));
await this.afterWrite();
return result;
}
+ /**
+ * Creates a user, making them an admin if the table was empty.
+ *
+ * The check and the insert have to be one transaction: two people signing up
+ * at once would otherwise both see an empty table and both become admin.
+ *
+ * The two branches are not a style choice. better-sqlite3 is synchronous and
+ * rejects an async transaction callback outright — "Transaction function
+ * cannot return a promise" — so a single body cannot serve both. It fails
+ * loudly rather than silently skipping the write, which is the one mercy here.
+ */
+ private async createCheckingIfFirst(
+ build: (isFirstUser: boolean) => NewUserRecord,
+ ): Promise<{ user: UserRecord; isFirstUser: boolean }> {
+ if (this.context.dialect === "sqlite") {
+ /* eslint-disable no-restricted-syntax -- sqlite-only branch: the dialect
+ is checked directly above, and better-sqlite3 rejects an async
+ transaction callback, so this cannot use the shared helpers. */
+ return this.context.drizzle.transaction((tx) => {
+ const isFirstUser =
+ tx.select({ id: users.id }).from(users).all().length === 0;
+ const rows = tx
+ .insert(users)
+ .values(build(isFirstUser))
+ .returning()
+ .all();
+ return { user: rows[0], isFirstUser };
+ });
+ /* eslint-enable no-restricted-syntax */
+ }
+
+ return this.context.drizzle.transaction(async (tx) => {
+ const existing = await tx.select({ id: users.id }).from(users);
+ const isFirstUser = existing.length === 0;
+ const values = build(isFirstUser);
+
+ if (supportsReturning(this.context.dialect)) {
+ // eslint-disable-next-line no-restricted-syntax -- guarded by the check on this line
+ const rows = await tx.insert(users).values(values).returning();
+ return { user: rows[0], isFirstUser };
+ }
+
+ // users is keyed by a text id the caller supplies, so there is something
+ // to read back by even without RETURNING.
+ await tx.insert(users).values(values);
+ const [user] = await tx
+ .select()
+ .from(users)
+ .where(eq(users.id, values.id));
+ return { user, isFirstUser };
+ });
+ }
+
async update(id: string, update: UserUpdate): Promise {
- const rows = await this.context.drizzle
- .update(users)
- .set(update)
- .where(eq(users.id, id))
- .returning();
+ const rows = await updateReturning(
+ this.context,
+ users,
+ update,
+ eq(users.id, id),
+ );
await this.afterWrite();
return rows[0] ?? null;
}
async delete(id: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(users)
- .where(eq(users.id, id))
- .returning({ id: users.id });
+ .where(eq(users.id, id));
await this.afterWrite();
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async countAdmins(): Promise {
diff --git a/src/backend/database/repositories/vault-profile-repository.ts b/src/backend/database/repositories/vault-profile-repository.ts
index 64ff3706..bcf8cec5 100644
--- a/src/backend/database/repositories/vault-profile-repository.ts
+++ b/src/backend/database/repositories/vault-profile-repository.ts
@@ -2,6 +2,12 @@ import { desc, eq, or } from "drizzle-orm";
import { randomUUID } from "crypto";
import { vaultProfiles } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import {
+ deleteReturning,
+ insertReturning,
+ updateReturning,
+} from "./returning.js";
export type VaultProfileRecord = typeof vaultProfiles.$inferSelect;
@@ -45,26 +51,23 @@ export class VaultProfileRepository {
}
async create(input: VaultProfileCreateInput): Promise {
- const [created] = await this.context.drizzle
- .insert(vaultProfiles)
- .values({
- syncId: randomUUID(),
- userId: input.userId,
- name: input.name,
- description: input.description,
- folder: input.folder,
- tags: input.tags,
- vaultAddr: input.vaultAddr,
- vaultNamespace: input.vaultNamespace,
- oidcMount: input.oidcMount,
- oidcRole: input.oidcRole,
- sshMount: input.sshMount,
- sshRole: input.sshRole,
- validPrincipals: input.validPrincipals,
- keyType: input.keyType,
- shared: input.shared ?? false,
- })
- .returning();
+ const [created] = await insertReturning(this.context, vaultProfiles, {
+ syncId: randomUUID(),
+ userId: input.userId,
+ name: input.name,
+ description: input.description,
+ folder: input.folder,
+ tags: input.tags,
+ vaultAddr: input.vaultAddr,
+ vaultNamespace: input.vaultNamespace,
+ oidcMount: input.oidcMount,
+ oidcRole: input.oidcRole,
+ sshMount: input.sshMount,
+ sshRole: input.sshRole,
+ validPrincipals: input.validPrincipals,
+ keyType: input.keyType,
+ shared: input.shared ?? false,
+ });
await this.afterWrite();
return created;
@@ -84,14 +87,15 @@ export class VaultProfileRepository {
id: number,
input: VaultProfileUpdateInput,
): Promise {
- const [updated] = await this.context.drizzle
- .update(vaultProfiles)
- .set({
+ const [updated] = await updateReturning(
+ this.context,
+ vaultProfiles,
+ {
...input,
updatedAt: input.updatedAt ?? new Date().toISOString(),
- })
- .where(eq(vaultProfiles.id, id))
- .returning();
+ },
+ eq(vaultProfiles.id, id),
+ );
if (updated) {
await this.afterWrite();
@@ -101,27 +105,27 @@ export class VaultProfileRepository {
}
async deleteById(id: number): Promise<{ syncId: string | null } | null> {
- const rows = await this.context.drizzle
- .delete(vaultProfiles)
- .where(eq(vaultProfiles.id, id))
- .returning({ syncId: vaultProfiles.syncId });
+ const rows = await deleteReturning(
+ this.context,
+ vaultProfiles,
+ eq(vaultProfiles.id, id),
+ );
if (rows.length === 0) return null;
await this.afterWrite();
- return rows[0];
+ return { syncId: rows[0].syncId };
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(vaultProfiles)
- .where(eq(vaultProfiles.userId, userId))
- .returning({ id: vaultProfiles.id });
+ .where(eq(vaultProfiles.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/vault-token-repository.ts b/src/backend/database/repositories/vault-token-repository.ts
index 1db262f0..a14ea70b 100644
--- a/src/backend/database/repositories/vault-token-repository.ts
+++ b/src/backend/database/repositories/vault-token-repository.ts
@@ -1,6 +1,8 @@
import { and, eq } from "drizzle-orm";
import { vaultTokens } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { upsert } from "./returning.js";
export type VaultTokenRecord = typeof vaultTokens.$inferSelect;
@@ -22,16 +24,17 @@ export class VaultTokenRepository {
async upsert(input: VaultTokenUpsertInput): Promise {
const createdAt = input.createdAt ?? new Date().toISOString();
- await this.context.drizzle
- .insert(vaultTokens)
- .values({
+ await upsert(
+ this.context,
+ vaultTokens,
+ {
userId: input.userId,
profileId: input.profileId,
sshCert: input.sshCert,
privateKey: input.privateKey,
expiresAt: input.expiresAt,
- })
- .onConflictDoUpdate({
+ },
+ {
target: [vaultTokens.userId, vaultTokens.profileId],
set: {
sshCert: input.sshCert,
@@ -39,7 +42,8 @@ export class VaultTokenRepository {
expiresAt: input.expiresAt,
createdAt,
},
- });
+ },
+ );
await this.afterWrite();
}
@@ -67,7 +71,7 @@ export class VaultTokenRepository {
profileId: number,
lastUsed = new Date().toISOString(),
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.update(vaultTokens)
.set({ lastUsed })
.where(
@@ -75,48 +79,45 @@ export class VaultTokenRepository {
eq(vaultTokens.userId, userId),
eq(vaultTokens.profileId, profileId),
),
- )
- .returning({ id: vaultTokens.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserAndProfile(
userId: string,
profileId: number,
): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(vaultTokens)
.where(
and(
eq(vaultTokens.userId, userId),
eq(vaultTokens.profileId, profileId),
),
- )
- .returning({ id: vaultTokens.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
async deleteByUserId(userId: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(vaultTokens)
- .where(eq(vaultTokens.userId, userId))
- .returning({ id: vaultTokens.id });
+ .where(eq(vaultTokens.userId, userId));
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length;
+ return rowsAffected(result);
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/repositories/webauthn-credential-repository.ts b/src/backend/database/repositories/webauthn-credential-repository.ts
index f5dac86d..5810cade 100644
--- a/src/backend/database/repositories/webauthn-credential-repository.ts
+++ b/src/backend/database/repositories/webauthn-credential-repository.ts
@@ -1,6 +1,8 @@
import { and, eq } from "drizzle-orm";
import { webauthnCredentials } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+import { rowsAffected } from "./mutation-result.js";
+import { insertReturning } from "./returning.js";
export type WebauthnCredentialRecord = typeof webauthnCredentials.$inferSelect;
export type NewWebauthnCredentialRecord =
@@ -41,10 +43,11 @@ export class WebauthnCredentialRepository {
async create(
record: NewWebauthnCredentialRecord,
): Promise {
- const rows = await this.context.drizzle
- .insert(webauthnCredentials)
- .values(record)
- .returning();
+ const rows = await insertReturning(
+ this.context,
+ webauthnCredentials,
+ record,
+ );
await this.afterWrite();
return rows[0];
@@ -63,21 +66,20 @@ export class WebauthnCredentialRepository {
}
async deleteForUser(userId: string, id: string): Promise {
- const rows = await this.context.drizzle
+ const result = await this.context.drizzle
.delete(webauthnCredentials)
.where(
and(
eq(webauthnCredentials.id, id),
eq(webauthnCredentials.userId, userId),
),
- )
- .returning({ id: webauthnCredentials.id });
+ );
- if (rows.length > 0) {
+ if (rowsAffected(result) > 0) {
await this.afterWrite();
}
- return rows.length > 0;
+ return rowsAffected(result) > 0;
}
private async afterWrite(): Promise {
diff --git a/src/backend/database/routes/audit-log-routes.ts b/src/backend/database/routes/audit-log-routes.ts
index 91273b91..60dea304 100644
--- a/src/backend/database/routes/audit-log-routes.ts
+++ b/src/backend/database/routes/audit-log-routes.ts
@@ -5,6 +5,12 @@ import {
createCurrentUserRepository,
} from "../repositories/factory.js";
import { apiLogger } from "../../utils/logger.js";
+import { exportFilename, toCsv, toNdjson } from "../../utils/audit-export.js";
+import {
+ logAudit,
+ getAuditUsername,
+ getRequestMeta,
+} from "../../utils/audit-logger.js";
async function isAdminUser(userId: string | undefined): Promise {
if (!userId) return false;
@@ -132,4 +138,125 @@ export function registerAuditLogRoutes(
.json({ error: "Failed to fetch audit log actions" });
}
});
+
+ /**
+ * @openapi
+ * /audit-logs/export:
+ * get:
+ * summary: Export audit logs
+ * description: Streams the full filtered result set as CSV or NDJSON. Accepts the same filters as GET /audit-logs. Admin only. The export is itself audited.
+ * tags:
+ * - Audit
+ * parameters:
+ * - in: query
+ * name: format
+ * schema: { type: string, enum: [csv, ndjson], default: csv }
+ * - in: query
+ * name: userId
+ * schema: { type: string }
+ * - in: query
+ * name: action
+ * schema: { type: string }
+ * - in: query
+ * name: resourceType
+ * schema: { type: string }
+ * - in: query
+ * name: success
+ * schema: { type: string, enum: [true, false] }
+ * - in: query
+ * name: startDate
+ * schema: { type: string, format: date-time }
+ * - in: query
+ * name: endDate
+ * schema: { type: string, format: date-time }
+ * responses:
+ * 200:
+ * description: Audit log file.
+ * 403:
+ * description: Not authorized.
+ * 500:
+ * description: Failed to export audit logs.
+ */
+ router.get("/audit-logs/export", authenticateJWT, async (req, res) => {
+ const authReq = req as AuthenticatedRequest;
+ try {
+ if (!(await isAdminUser(authReq.userId))) {
+ return res.status(403).json({ error: "Not authorized" });
+ }
+
+ const format = req.query.format === "ndjson" ? "ndjson" : "csv";
+ const { userId, action, resourceType, success, startDate, endDate } =
+ req.query as Record;
+ const filters = {
+ userId,
+ action,
+ resourceType,
+ success:
+ success !== undefined && success !== ""
+ ? success === "true"
+ : undefined,
+ startDate,
+ endDate,
+ };
+
+ res.setHeader(
+ "Content-Type",
+ format === "csv" ? "text/csv; charset=utf-8" : "application/x-ndjson",
+ );
+ res.setHeader(
+ "Content-Disposition",
+ `attachment; filename="${exportFilename(format, new Date())}"`,
+ );
+
+ // Streamed in batches: an export is unbounded by definition, and the
+ // whole point is to move data out before retention drops it.
+ const BATCH = 500;
+ let offset = 0;
+ let exported = 0;
+
+ for (;;) {
+ const rows = await createCurrentAuditLogRepository().listForExport({
+ filters,
+ limit: BATCH,
+ offset,
+ });
+ if (rows.length === 0) break;
+
+ if (format === "csv") {
+ // Header only on the first batch.
+ const chunk = toCsv(rows);
+ res.write(
+ offset === 0 ? chunk : chunk.slice(chunk.indexOf("\n") + 1),
+ );
+ } else {
+ res.write(toNdjson(rows));
+ }
+
+ exported += rows.length;
+ offset += rows.length;
+ if (rows.length < BATCH) break;
+ }
+
+ res.end();
+
+ // Reading the whole trail is itself worth recording.
+ const { ipAddress, userAgent } = getRequestMeta(req);
+ void logAudit({
+ userId: authReq.userId!,
+ username: await getAuditUsername(authReq.userId!),
+ action: "export_audit_logs",
+ resourceType: "audit_log",
+ details: JSON.stringify({ format, exported, filters }),
+ ipAddress,
+ userAgent,
+ success: true,
+ });
+ } catch (err) {
+ apiLogger.error("Failed to export audit logs", err);
+ if (!res.headersSent) {
+ return res.status(500).json({ error: "Failed to export audit logs" });
+ }
+ res.end();
+ }
+ });
}
diff --git a/src/backend/database/routes/credentials.ts b/src/backend/database/routes/credentials.ts
index bd5d49b3..3772e775 100644
--- a/src/backend/database/routes/credentials.ts
+++ b/src/backend/database/routes/credentials.ts
@@ -6,12 +6,15 @@ import { AuthManager } from "../../utils/auth-manager.js";
import { parseSSHKey } from "../../utils/ssh-key-utils.js";
import { registerCredentialKeyRoutes } from "./credential-key-routes.js";
import { registerCredentialDeployRoutes } from "./credential-deploy-routes.js";
-import { logAudit, getRequestMeta } from "../../utils/audit-logger.js";
+import {
+ logAudit,
+ getAuditUsername,
+ getRequestMeta,
+} from "../../utils/audit-logger.js";
import {
createCurrentCredentialRepository,
createCurrentHostResolutionRepository,
createCurrentHostRepository,
- createCurrentUserRepository,
createCurrentSyncTombstoneRepository,
} from "../repositories/factory.js";
@@ -25,11 +28,6 @@ const authManager = AuthManager.getInstance();
const authenticateJWT = authManager.createAuthMiddleware();
const requireDataAccess = authManager.createDataAccessMiddleware();
-async function getAuditUsername(userId: string): Promise {
- const actor = await createCurrentUserRepository().findById(userId);
- return actor?.username ?? userId;
-}
-
/**
* @openapi
* /credentials:
diff --git a/src/backend/database/routes/delete-user-data.ts b/src/backend/database/routes/delete-user-data.ts
index b3ec33e1..268cd92d 100644
--- a/src/backend/database/routes/delete-user-data.ts
+++ b/src/backend/database/routes/delete-user-data.ts
@@ -44,7 +44,9 @@ export async function deleteUserAndRelatedData(userId: string): Promise {
userId,
);
- await createCurrentSessionRecordingRepository().deleteByUserId(userId);
+ // Retained rather than deleted: these outlive the account by design.
+ // See anonymizeByUserId on each repository.
+ await createCurrentSessionRecordingRepository().anonymizeByUserId(userId);
await createCurrentRbacAccessRepository().deleteHostAccessForUserReferences(
userId,
@@ -56,7 +58,7 @@ export async function deleteUserAndRelatedData(userId: string): Promise {
await createCurrentRoleRepository().removeAllRolesFromUser(userId);
await createCurrentAlertRepository().deleteByUserId(userId);
- await createCurrentAuditLogRepository().deleteByUserId(userId);
+ await createCurrentAuditLogRepository().anonymizeByUserId(userId);
await createCurrentSshCredentialUsageRepository().deleteByUserId(userId);
diff --git a/src/backend/database/routes/homepage-proxy-routes.ts b/src/backend/database/routes/homepage-proxy-routes.ts
index eaf23a1b..6551b8b8 100644
--- a/src/backend/database/routes/homepage-proxy-routes.ts
+++ b/src/backend/database/routes/homepage-proxy-routes.ts
@@ -3,8 +3,9 @@ import express from "express";
import https from "https";
import http from "http";
import { lookup } from "dns/promises";
-import { BlockList, isIP } from "net";
+import { isIP } from "net";
import { homepageLogger } from "../../utils/logger.js";
+import { isBlockedAddress } from "../../utils/safe-outbound-fetch.js";
export const homepageProxyRouter = express.Router();
@@ -17,40 +18,6 @@ const proxyCache = new Map();
const CACHE_SIZE = 50;
const FETCH_TIMEOUT_MS = 8000;
-const blockedAddresses = new BlockList();
-for (const [network, prefix] of [
- ["0.0.0.0", 8],
- ["10.0.0.0", 8],
- ["100.64.0.0", 10],
- ["127.0.0.0", 8],
- ["169.254.0.0", 16],
- ["172.16.0.0", 12],
- ["192.168.0.0", 16],
- ["198.18.0.0", 15],
- ["224.0.0.0", 4],
- ["240.0.0.0", 4],
-] as const) {
- blockedAddresses.addSubnet(network, prefix, "ipv4");
-}
-for (const [network, prefix] of [
- ["::", 128],
- ["::1", 128],
- ["::ffff:0:0", 96],
- ["fc00::", 7],
- ["fe80::", 10],
- ["ff00::", 8],
-] as const) {
- blockedAddresses.addSubnet(network, prefix, "ipv6");
-}
-
-function isBlockedAddress(address: string): boolean {
- const family = isIP(address);
- return (
- family === 0 ||
- blockedAddresses.check(address, family === 4 ? "ipv4" : "ipv6")
- );
-}
-
async function resolvePublicUrl(rawUrl: string): Promise<{
url: URL;
address: string;
diff --git a/src/backend/database/routes/host-normalizers.ts b/src/backend/database/routes/host-normalizers.ts
index 9c17ac07..ff06d869 100644
--- a/src/backend/database/routes/host-normalizers.ts
+++ b/src/backend/database/routes/host-normalizers.ts
@@ -1,3 +1,5 @@
+import type { AuthOverrideProtocol } from "../../../types/auth-protocols.js";
+
export function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
@@ -6,6 +8,56 @@ export function isValidPort(port: unknown): port is number {
return typeof port === "number" && port > 0 && port <= 65535;
}
+export function isOptionalBoolean(
+ value: unknown,
+): value is boolean | undefined {
+ return value === undefined || typeof value === "boolean";
+}
+
+export const OWNER_PRIVATE_AUTH_FIELDS = {
+ ssh: [
+ "authType",
+ "authMethod",
+ "credentialId",
+ "vaultProfileId",
+ "overrideCredentialUsername",
+ "shareSshAuth",
+ "password",
+ "key",
+ "keyPassword",
+ "keyType",
+ "sudoPassword",
+ ],
+ rdp: [
+ "rdpAuthType",
+ "rdpCredentialId",
+ "rdpUser",
+ "rdpPassword",
+ "rdpDomain",
+ ],
+ vnc: ["vncAuthType", "vncCredentialId", "vncUser", "vncPassword"],
+ telnet: [
+ "telnetAuthType",
+ "telnetCredentialId",
+ "telnetUser",
+ "telnetPassword",
+ ],
+} as const satisfies Record