artifacty 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +54 -8
- package/README.md +56 -10
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/docs/README.md +6 -0
- package/docs/ko/README.md +6 -0
- package/docs/ko/language-packs.md +40 -0
- package/docs/ko/smtp.md +27 -0
- package/docs/ko/upgrade-backups.md +16 -0
- package/docs/language-packs.md +38 -0
- package/docs/roadmap-design.md +31 -2
- package/docs/smtp.md +25 -0
- package/docs/upgrade-backups.md +16 -0
- package/package.json +10 -1
- package/scripts/build-ui.mjs +16 -0
- package/src/cli.js +4 -4
- package/src/client/api-docs.js +32 -0
- package/src/client/auto-apply.js +20 -0
- package/src/client/editor.js +75 -20
- package/src/client/sidebar.js +31 -0
- package/src/client/theme.js +42 -0
- package/src/client/ui-controls.css +2 -0
- package/src/client/update.js +33 -0
- package/src/client/viewer.js +17 -6
- package/src/lib/background.js +3 -1
- package/src/lib/events.js +4 -4
- package/src/lib/i18n.js +601 -9
- package/src/lib/language-settings.js +114 -0
- package/src/lib/openapi.js +93 -74
- package/src/lib/render.js +888 -628
- package/src/lib/sample-content.js +6 -6
- package/src/lib/self-update.js +12 -0
- package/src/lib/smtp-client.js +206 -0
- package/src/lib/smtp-settings.js +109 -0
- package/src/lib/storage.js +71 -17
- package/src/lib/ui-icons.js +32 -0
- package/src/lib/web-update.js +177 -0
- package/src/mcp-server.js +3 -3
- package/src/server.js +250 -18
- package/src/styles/ui-controls.scss +63 -0
- package/src/update-runner.js +7 -0
package/docs/roadmap-design.md
CHANGED
|
@@ -12,8 +12,9 @@ Every feature follows these architecture rules:
|
|
|
12
12
|
- Treat artifact content as untrusted.
|
|
13
13
|
- Add no runtime dependency unless the section explicitly permits it.
|
|
14
14
|
|
|
15
|
-
Status recorded on 2026-09-04:
|
|
16
|
-
`STORE_VERSION` was `8`.
|
|
15
|
+
Status recorded on 2026-09-04: the original sections 1–21 were implemented, and
|
|
16
|
+
`STORE_VERSION` was `8`. Their original planning text follows. Section 22 is a
|
|
17
|
+
new proposal deferred to the next version; it is not a current capability.
|
|
17
18
|
|
|
18
19
|
Store schema changes increase `STORE_VERSION` (originally `4`). They use the
|
|
19
20
|
existing `ensureColumn` / `CREATE TABLE IF NOT EXISTS` migration path. Older
|
|
@@ -42,6 +43,7 @@ stores upgrade on first access.
|
|
|
42
43
|
19. [OpenAPI Specification](#19-openapi-specification)
|
|
43
44
|
20. [MCP Protocol Refresh](#20-mcp-protocol-refresh)
|
|
44
45
|
21. [Delivery Plan](#21-delivery-plan)
|
|
46
|
+
22. [Optional Artifact Encryption at Rest — Planned](#22-optional-artifact-encryption-at-rest--planned)
|
|
45
47
|
|
|
46
48
|
---
|
|
47
49
|
|
|
@@ -987,3 +989,30 @@ If the artifact envelope changes, use a `v2` schema document. Increase
|
|
|
987
989
|
check.
|
|
988
990
|
- `/raw` always returns the stored bytes unchanged.
|
|
989
991
|
- No generated MCP config hard-codes `http://127.0.0.1:8787`.
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## 22. Optional Artifact Encryption at Rest — Planned
|
|
997
|
+
|
|
998
|
+
**Status (2026-10-14): deferred to the next version. Design and implementation
|
|
999
|
+
are pending.** This is a substantial storage change, not a current feature.
|
|
1000
|
+
Existing SMTP configuration encryption does not encrypt artifact data.
|
|
1001
|
+
|
|
1002
|
+
An administrator should be able to opt in or turn encryption off. Each change
|
|
1003
|
+
must migrate the existing store between encrypted and plaintext states; a
|
|
1004
|
+
setting that affects only future writes is insufficient. The design must cover
|
|
1005
|
+
artifact/version blobs and bundle assets, metadata, and search indexes, including
|
|
1006
|
+
any retained plaintext copies or derived data. Cipher and storage choices remain
|
|
1007
|
+
open until the full design is reviewed.
|
|
1008
|
+
|
|
1009
|
+
Before migration, create and verify a recovery backup and check available disk
|
|
1010
|
+
space. Hold an exclusive write lock across HTTP, CLI, MCP, and background writers.
|
|
1011
|
+
Verify file references, hashes, and database/index integrity before committing
|
|
1012
|
+
the new state. Define how failures and interrupted migrations recover without
|
|
1013
|
+
leaving a mixed or silently unreadable store.
|
|
1014
|
+
|
|
1015
|
+
Keep encryption keys and recovery material separately secured. The design must
|
|
1016
|
+
specify key backup, restore to a new home, key-loss behavior, and whether disabling
|
|
1017
|
+
encryption leaves any recoverable plaintext or ciphertext copies. Review backup,
|
|
1018
|
+
retention, and recovery behavior before implementing the administrator control.
|
package/docs/smtp.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# SMTP and password reset
|
|
2
|
+
|
|
3
|
+
English | [한국어](ko/smtp.md)
|
|
4
|
+
|
|
5
|
+
## Configure email
|
|
6
|
+
|
|
7
|
+
As an administrator, open **Administration → Email / SMTP** (`/admin/email`). Enter the SMTP host, port, sender address, and TLS mode. Enable delivery when the configuration is ready. Use your own mail service settings; this guide supplies no provider credentials.
|
|
8
|
+
|
|
9
|
+
TLS or STARTTLS is required. Artifacty validates the server certificate and does not fall back to an insecure connection. If the username is empty, SMTP authentication is disabled and the stored password is cleared. With a username, an empty password field keeps the existing password.
|
|
10
|
+
|
|
11
|
+
## Reset a user's password
|
|
12
|
+
|
|
13
|
+
At `/admin/users`, an administrator can reset a user's password. Artifacty generates a temporary password, requires a password change at the next login, and revokes the user's sessions. Existing API tokens are retained.
|
|
14
|
+
|
|
15
|
+
If SMTP delivery is enabled, Artifacty sends the temporary password to the user's email address. SMTP acceptance does not guarantee inbox delivery. A failed or uncertain send is not retried automatically; the message may already have been accepted. Use the temporary password shown once in the administrator result for manual delivery. Do not repeat the reset solely to retry email.
|
|
16
|
+
|
|
17
|
+
## Protect the encryption key
|
|
18
|
+
|
|
19
|
+
Artifacty encrypts the complete SMTP configuration in SQLite with AES-256-GCM. The separate key is `ARTIFACTY_HOME/secrets/smtp.key`. On POSIX systems, the directory requires mode `700` and the key file mode `600`.
|
|
20
|
+
|
|
21
|
+
Full JSON backups carry the encrypted configuration, but not its key. Upgrade snapshots also exclude the key. A restore into the same home leaves the existing key untouched. When moving or restoring into a new home, back up and transfer the original key separately with its private permissions.
|
|
22
|
+
|
|
23
|
+
If the key is lost, the encrypted configuration cannot be recovered. Restore the original key before changing settings. Do not generate a replacement key to try to decrypt the old configuration. Keep the key backup private and separate from shared backup bundles.
|
|
24
|
+
|
|
25
|
+
See [upgrade snapshots and recovery](upgrade-backups.md) for store restore procedures.
|
package/docs/upgrade-backups.md
CHANGED
|
@@ -29,6 +29,22 @@ The command uses POSIX Linux shell syntax. After a successful update, it puts `~
|
|
|
29
29
|
|
|
30
30
|
The `latest` tag must resolve to version 0.14.0 or newer to include the updater.
|
|
31
31
|
|
|
32
|
+
### Update from the browser
|
|
33
|
+
|
|
34
|
+
Sign in with an administrator browser session. Open **Administration → Server update** (`/admin/update`). API tokens do not replace that session for these routes.
|
|
35
|
+
|
|
36
|
+
1. Select **Check for updates**. This checks the latest npm release without installing or restarting the service.
|
|
37
|
+
2. If browser updates are supported and a newer version is available, review the displayed target version.
|
|
38
|
+
3. Select the confirmation checkbox for backup and restart.
|
|
39
|
+
4. Select **Back up and update**.
|
|
40
|
+
5. Keep the page open while the server restarts. Read the update result after reconnection.
|
|
41
|
+
|
|
42
|
+
Confirmation expires after ten minutes and belongs to the browser session that checked the version. If it expires or the installed version changes, check again. Form submissions require the exact same origin as the server. No password re-entry is required.
|
|
43
|
+
|
|
44
|
+
Browser installation requires this server to be the non-root Linux systemd user service. Other installations can check the latest release, but cannot start the update from this page. The worker runs in an independent systemd transient service, so stopping the Artifacty server does not stop the update worker. Status is stored outside the artifact store under `~/.local/share/artifacty/update-jobs`.
|
|
45
|
+
|
|
46
|
+
Opening the page or reading status does not contact npm or install a package. Status checks can inspect systemd to identify a stopped worker. If dispatch is uncertain, the job remains reserved. An interrupted worker or incomplete recovery requires manual inspection before another update; it is not reported as success. Keep the retained snapshot and releases until recovery is verified.
|
|
47
|
+
|
|
32
48
|
### Automatic installation limits
|
|
33
49
|
|
|
34
50
|
Automatic installation and restart support Linux with the systemd user service `com.artifacty.server`. Run the updater as the service's user. The unit must use the simple `ExecStart` form produced by Artifacty's installer and must explicitly pass `--home` for the selected store. Unsupported command expansion or quoting stops the update before service shutdown. Automatic updates reject root execution. Do not run the updater as root to replace a root-owned global package.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artifacty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Local artifact exchange for heterogeneous LLM agents via HTTP and MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"mcp": "node src/mcp-server.js",
|
|
30
30
|
"test": "node --test",
|
|
31
31
|
"lint": "node scripts/lint.mjs",
|
|
32
|
+
"build:ui": "node scripts/build-ui.mjs",
|
|
32
33
|
"smoke": "bash scripts/smoke.sh",
|
|
33
34
|
"release:check": "npm run lint && npm test && npm run smoke"
|
|
34
35
|
},
|
|
@@ -38,15 +39,18 @@
|
|
|
38
39
|
"docs/assets/artifacty.png",
|
|
39
40
|
"docs/central-team-deployment-design.md",
|
|
40
41
|
"docs/integrations.md",
|
|
42
|
+
"docs/language-packs.md",
|
|
41
43
|
"docs/mcp-public-api.md",
|
|
42
44
|
"docs/network-sharing.md",
|
|
43
45
|
"docs/release-checklist.md",
|
|
44
46
|
"docs/roadmap-design.md",
|
|
45
47
|
"docs/sarif-csv-artifact-plan.md",
|
|
48
|
+
"docs/smtp.md",
|
|
46
49
|
"docs/threat-model.md",
|
|
47
50
|
"docs/upgrade-backups.md",
|
|
48
51
|
"docs/writing-style.md",
|
|
49
52
|
"scripts/lint.mjs",
|
|
53
|
+
"scripts/build-ui.mjs",
|
|
50
54
|
"scripts/smoke.sh",
|
|
51
55
|
"README.md",
|
|
52
56
|
"LICENSE",
|
|
@@ -76,5 +80,10 @@
|
|
|
76
80
|
"@babel/standalone": "^8.0.4",
|
|
77
81
|
"react": "^18.3.1",
|
|
78
82
|
"react-dom": "^18.3.1"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@tabler/icons": "^3.49.0",
|
|
86
|
+
"bootstrap": "^5.3.8",
|
|
87
|
+
"sass": "^1.105.1"
|
|
79
88
|
}
|
|
80
89
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import * as sass from 'sass';
|
|
3
|
+
const root = new URL('../', import.meta.url);
|
|
4
|
+
const result = sass.compile(new URL('src/styles/ui-controls.scss', root).pathname, {
|
|
5
|
+
loadPaths: [new URL('node_modules', root).pathname], style: 'compressed',
|
|
6
|
+
quietDeps: true, silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'if-function'],
|
|
7
|
+
});
|
|
8
|
+
await writeFile(new URL('src/client/ui-controls.css', root), '/*! Bootstrap v5.3.8 | MIT | https://getbootstrap.com/ | Generated by npm run build:ui */\n' + result.css + '\n');
|
|
9
|
+
const names = ['layout-sidebar-left-collapse','layout-sidebar-left-expand','files','inbox','plus','upload','settings','users','database-export','archive','webhook','refresh','language','mail','user-circle','key','logout','login','code','chevron-down','device-desktop','sun','moon'];
|
|
10
|
+
const icons = {};
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
const svg = await readFile(new URL(`node_modules/@tabler/icons/icons/outline/${name}.svg`, root), 'utf8');
|
|
13
|
+
icons[name] = svg.slice(svg.indexOf('>') + 1, svg.lastIndexOf('</svg>')).trim().replace(/\s+/g, ' ');
|
|
14
|
+
}
|
|
15
|
+
await writeFile(new URL('src/lib/ui-icons.js', root), `// Generated from @tabler/icons v3.49.0 (MIT), by npm run build:ui.\nconst paths = ${JSON.stringify(icons, null, 2)};\n\nexport function uiIcon(name, { size = 18 } = {}) {\n if (!Object.hasOwn(paths, name)) throw new Error('Unknown UI icon');\n const dimension = [16, 18, 20, 24].includes(size) ? size : 18;\n return '<svg class="ui-icon" width="' + dimension + '" height="' + dimension + '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">' + paths[name] + '</svg>';\n}\n`);
|
|
16
|
+
console.log('Built local Bootstrap controls and 23 Tabler icons.');
|
package/src/cli.js
CHANGED
|
@@ -503,7 +503,7 @@ async function main() {
|
|
|
503
503
|
const id = options._[0];
|
|
504
504
|
const visibility = options._[1];
|
|
505
505
|
if (!id || !visibility) {
|
|
506
|
-
throw new Error("visibility requires an artifact id and a value: private or
|
|
506
|
+
throw new Error("visibility requires an artifact id and a value: private, team, or public");
|
|
507
507
|
}
|
|
508
508
|
const artifact = await setArtifactVisibility(store, id, visibility, { audit: cliAuditContext() });
|
|
509
509
|
printJson(await withUrls(store, artifact));
|
|
@@ -1006,17 +1006,17 @@ Usage:
|
|
|
1006
1006
|
artifacty status [--home ~/.artifacty]
|
|
1007
1007
|
artifacty stop [--home ~/.artifacty] [--timeout 30000] [--force]
|
|
1008
1008
|
artifacty doctor [--home ~/.artifacty] [--skip-mcp] [--timeout 5000]
|
|
1009
|
-
artifacty publish --title <title> (--file <path> | --content <text>) [--format html|markdown|text|json|code|svg|mermaid|react|notebook] [--source agent] [--tag tag] [--visibility private|team]
|
|
1009
|
+
artifacty publish --title <title> (--file <path> | --content <text>) [--format html|markdown|text|json|code|svg|mermaid|react|notebook] [--source agent] [--tag tag] [--visibility private|team|public]
|
|
1010
1010
|
artifacty import --agent claude|codex|gemini|copilot|cursor|auto (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react|notebook] [--tag tag]
|
|
1011
1011
|
artifacty install claude|codex|gemini|copilot|cursor|all [--dry-run] [--config <path>] [--server-path <path>] [--url http://127.0.0.1:8787] [--mcp-url http://127.0.0.1:8787/mcp] [--api-token token] [--transport local|bridge] [--timeout 30000]
|
|
1012
1012
|
artifacty check [--server-path <path>] [--timeout 5000]
|
|
1013
1013
|
artifacty update [--check] [--target-version <version>] [--home <path>]
|
|
1014
1014
|
artifacty upgrade [--check] [--target-version <version>] [--home <path>]
|
|
1015
|
-
artifacty update <id> (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react|notebook] [--expected-version N] [--visibility private|team]
|
|
1015
|
+
artifacty update <id> (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react|notebook] [--expected-version N] [--visibility private|team|public]
|
|
1016
1016
|
artifacty samples install|remove
|
|
1017
1017
|
artifacty archive <id>
|
|
1018
1018
|
artifacty restore <id>
|
|
1019
|
-
artifacty visibility <id> private|team
|
|
1019
|
+
artifacty visibility <id> private|team|public
|
|
1020
1020
|
artifacty audit [--artifact <id>] [--limit 100]
|
|
1021
1021
|
artifacty index rebuild [--embeddings]
|
|
1022
1022
|
artifacty integrity
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Progressive enhancement only: native disclosures and examples work without JavaScript.
|
|
2
|
+
export function matchesOperation(searchText, query) {
|
|
3
|
+
return query.trim().toLowerCase().split(/\s+/).every((word) => searchText.toLowerCase().includes(word));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function enhanceApiDocs(document, clipboard) {
|
|
7
|
+
const search = document.querySelector('#api-search');
|
|
8
|
+
const operations = [...document.querySelectorAll('[data-operation]')];
|
|
9
|
+
if (search) {
|
|
10
|
+
document.querySelector('[data-search-controls]').hidden = false;
|
|
11
|
+
search.addEventListener('input', () => {
|
|
12
|
+
for (const operation of operations) operation.hidden = !matchesOperation(operation.dataset.search, search.value);
|
|
13
|
+
for (const group of document.querySelectorAll('[data-tag-group]')) group.hidden = ![...group.querySelectorAll('[data-operation]')].some((operation) => !operation.hidden);
|
|
14
|
+
document.querySelector('[data-no-results]').hidden = operations.some((operation) => !operation.hidden);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!clipboard?.writeText) return;
|
|
18
|
+
for (const button of document.querySelectorAll('[data-copy]')) {
|
|
19
|
+
button.hidden = false;
|
|
20
|
+
button.addEventListener('click', async () => {
|
|
21
|
+
const status = document.querySelector('[data-copy-status]');
|
|
22
|
+
try {
|
|
23
|
+
await clipboard.writeText(button.parentElement.querySelector('code').textContent);
|
|
24
|
+
status.textContent = document.body.dataset.copySuccess;
|
|
25
|
+
} catch {
|
|
26
|
+
status.textContent = document.body.dataset.copyFailure;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof document !== 'undefined') enhanceApiDocs(document, navigator.clipboard);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Only opt-in GET forms auto-apply. Account and server mutations stay explicit.
|
|
2
|
+
export function enhanceAutoApply(document) {
|
|
3
|
+
for (const form of document.querySelectorAll('form[data-auto-apply]')) {
|
|
4
|
+
if (form.method.toLowerCase() !== 'get' || typeof form.requestSubmit !== 'function') continue;
|
|
5
|
+
const controls = form.dataset.autoApply === 'filters'
|
|
6
|
+
? form.querySelectorAll('.dashboard-advanced select, .dashboard-advanced input')
|
|
7
|
+
: form.querySelectorAll('select');
|
|
8
|
+
for (const control of controls) {
|
|
9
|
+
control.addEventListener('change', () => {
|
|
10
|
+
if (!form.reportValidity()) return;
|
|
11
|
+
form.requestSubmit();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const buttons = form.dataset.autoApply === 'filters'
|
|
15
|
+
? form.querySelectorAll('.dashboard-filter-actions')
|
|
16
|
+
: form.querySelectorAll('button[type="submit"]');
|
|
17
|
+
for (const button of buttons) button.hidden = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (typeof document !== 'undefined') enhanceAutoApply(document);
|
package/src/client/editor.js
CHANGED
|
@@ -331,28 +331,83 @@ function invalidJsonMessage(error) {
|
|
|
331
331
|
return messages.invalidJson.replaceAll("{message}", error.message);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
// Deliberately renders a small, escaped Markdown subset. Links and raw HTML
|
|
335
|
+
// remain text; previewing a document must not activate its URLs or scripts.
|
|
334
336
|
function markdownPreview(content) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
337
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
338
|
+
const output = [];
|
|
339
|
+
let paragraph = [];
|
|
340
|
+
let list = "";
|
|
341
|
+
const inline = (value) => escapeHtml(value).replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
342
|
+
const flush = () => {
|
|
343
|
+
if (paragraph.length) {
|
|
344
|
+
output.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
345
|
+
paragraph = [];
|
|
346
|
+
}
|
|
347
|
+
if (list) {
|
|
348
|
+
output.push(`</${list}>`);
|
|
349
|
+
list = "";
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const cells = (line) => line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
353
|
+
const tableRow = (line = "") => /^\s*\|.*\|\s*$/.test(line);
|
|
354
|
+
|
|
355
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
356
|
+
const line = lines[index];
|
|
357
|
+
const fence = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
358
|
+
if (fence) {
|
|
359
|
+
flush();
|
|
360
|
+
const language = /^[a-zA-Z0-9+#_.-]+$/.test(fence[2].trim()) ? fence[2].trim().toLowerCase() : "";
|
|
361
|
+
const source = [];
|
|
362
|
+
const closing = new RegExp(`^ {0,3}${fence[1][0]}{${fence[1].length},}\\s*$`);
|
|
363
|
+
while (++index < lines.length && !closing.test(lines[index])) source.push(lines[index]);
|
|
364
|
+
output.push(`<pre><code${language ? ` class="language-${language}"` : ""}>${escapeHtml(source.join("\n"))}</code></pre>`);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (!line.trim()) {
|
|
368
|
+
flush();
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const headers = cells(line);
|
|
372
|
+
const separators = cells(lines[index + 1] || "");
|
|
373
|
+
if (tableRow(line) && headers.length === separators.length && separators.every((cell) => /^:?-{3,}:?$/.test(cell))) {
|
|
374
|
+
flush();
|
|
375
|
+
const alignment = (column) => {
|
|
376
|
+
const value = separators[column];
|
|
377
|
+
return value.endsWith(":") ? (value.startsWith(":") ? "center" : "right") : "left";
|
|
378
|
+
};
|
|
379
|
+
const row = (values, tag) => `<tr>${headers.map((_, column) => `<${tag} style="text-align:${alignment(column)}">${inline(values[column] || "")}</${tag}>`).join("")}</tr>`;
|
|
380
|
+
output.push(`<div class="artifact-table-scroll"><table class="artifact-table"><thead>${row(headers, "th")}</thead><tbody>`);
|
|
381
|
+
index += 1;
|
|
382
|
+
while (tableRow(lines[index + 1])) output.push(row(cells(lines[++index]), "td"));
|
|
383
|
+
output.push("</tbody></table></div>");
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
|
|
387
|
+
if (heading) {
|
|
388
|
+
flush();
|
|
389
|
+
output.push(`<h${heading[1].length}>${inline(heading[2])}</h${heading[1].length}>`);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const item = /^([-*+] |\d+[.)] )\s*(.+)$/.exec(line);
|
|
393
|
+
if (item) {
|
|
394
|
+
const type = /^\d/.test(item[1]) ? "ol" : "ul";
|
|
395
|
+
if (paragraph.length || (list && list !== type)) flush();
|
|
396
|
+
if (!list) {
|
|
397
|
+
output.push(`<${type}>`);
|
|
398
|
+
list = type;
|
|
352
399
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
400
|
+
const task = /^\[([ xX])\]\s+(.+)$/.exec(item[2]);
|
|
401
|
+
output.push(task
|
|
402
|
+
? `<li class="task-list-item"><label><input type="checkbox" disabled${task[1].toLowerCase() === "x" ? " checked" : ""}> ${inline(task[2])}</label></li>`
|
|
403
|
+
: `<li>${inline(item[2])}</li>`);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (list) flush();
|
|
407
|
+
paragraph.push(line.trim());
|
|
408
|
+
}
|
|
409
|
+
flush();
|
|
410
|
+
return output.join("\n");
|
|
356
411
|
}
|
|
357
412
|
|
|
358
413
|
function escapeHtml(value) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
let sidebarCollapsed = false;
|
|
2
|
+
try { sidebarCollapsed = localStorage.getItem("artifacty_sidebar") === "collapsed"; } catch { /* Default expanded. */ }
|
|
3
|
+
|
|
4
|
+
function applySidebar(collapsed) {
|
|
5
|
+
sidebarCollapsed = collapsed;
|
|
6
|
+
if (collapsed) document.documentElement.setAttribute("data-sidebar", "collapsed");
|
|
7
|
+
else document.documentElement.removeAttribute("data-sidebar");
|
|
8
|
+
for (const button of document.querySelectorAll("[data-sidebar-toggle]")) {
|
|
9
|
+
button.setAttribute("aria-expanded", String(!collapsed));
|
|
10
|
+
const label = collapsed ? button.dataset.expandLabel : button.dataset.collapseLabel;
|
|
11
|
+
button.setAttribute("aria-label", label);
|
|
12
|
+
button.title = label;
|
|
13
|
+
button.querySelector("[data-sidebar-collapse-icon]").hidden = collapsed;
|
|
14
|
+
button.querySelector("[data-sidebar-expand-icon]").hidden = !collapsed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
applySidebar(sidebarCollapsed);
|
|
18
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
19
|
+
for (const button of document.querySelectorAll("[data-sidebar-toggle]")) {
|
|
20
|
+
button.hidden = false;
|
|
21
|
+
button.addEventListener("click", () => {
|
|
22
|
+
applySidebar(!sidebarCollapsed);
|
|
23
|
+
try { localStorage.setItem("artifacty_sidebar", sidebarCollapsed ? "collapsed" : "expanded"); } catch { /* Current page remains usable. */ }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
applySidebar(sidebarCollapsed);
|
|
27
|
+
});
|
|
28
|
+
window.addEventListener("storage", (event) => {
|
|
29
|
+
if (event.key !== "artifacty_sidebar" && event.key !== null) return;
|
|
30
|
+
try { applySidebar(localStorage.getItem("artifacty_sidebar") === "collapsed"); } catch { /* Retain the current page choice. */ }
|
|
31
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const themeChoices = new Set(["system", "light", "dark"]);
|
|
2
|
+
let selectedTheme = "system";
|
|
3
|
+
const systemTheme = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
4
|
+
|
|
5
|
+
function readSavedTheme() {
|
|
6
|
+
try {
|
|
7
|
+
const saved = localStorage.getItem("artifacty_theme");
|
|
8
|
+
return themeChoices.has(saved) ? saved : "system";
|
|
9
|
+
} catch {
|
|
10
|
+
return "system";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applyTheme(theme) {
|
|
15
|
+
selectedTheme = themeChoices.has(theme) ? theme : "system";
|
|
16
|
+
if (selectedTheme === "system") document.documentElement.removeAttribute("data-theme");
|
|
17
|
+
else document.documentElement.setAttribute("data-theme", selectedTheme);
|
|
18
|
+
document.documentElement.setAttribute("data-bs-theme", selectedTheme === "system" ? (systemTheme?.matches ? "dark" : "light") : selectedTheme);
|
|
19
|
+
for (const button of document.querySelectorAll("[data-theme-choice]")) {
|
|
20
|
+
button.setAttribute("aria-pressed", String(button.dataset.themeChoice === selectedTheme));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
applyTheme(readSavedTheme());
|
|
25
|
+
systemTheme?.addEventListener("change", () => { if (selectedTheme === "system") applyTheme("system"); });
|
|
26
|
+
|
|
27
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
28
|
+
for (const control of document.querySelectorAll("[data-theme-controls]")) control.hidden = false;
|
|
29
|
+
for (const button of document.querySelectorAll("[data-theme-choice]")) {
|
|
30
|
+
button.addEventListener("click", () => {
|
|
31
|
+
const choice = button.dataset.themeChoice;
|
|
32
|
+
if (!themeChoices.has(choice)) return;
|
|
33
|
+
applyTheme(choice);
|
|
34
|
+
try { localStorage.setItem("artifacty_theme", choice); } catch { /* Keep the current page usable when storage is blocked. */ }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
applyTheme(selectedTheme);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
window.addEventListener("storage", (event) => {
|
|
41
|
+
if (event.key === "artifacty_theme" || event.key === null) applyTheme(readSavedTheme());
|
|
42
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! Bootstrap v5.3.8 | MIT | https://getbootstrap.com/ | Generated by npm run build:ui */
|
|
2
|
+
:root,[data-bs-theme=light]{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-primary: #315ca8;--bs-secondary: #58616e;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #b42332;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-primary-rgb: 49, 92, 168;--bs-secondary-rgb: 88, 97, 110;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 180, 35, 50;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-primary-text-emphasis: rgb(7.6862745098%, 14.431372549%, 26.3529411765%);--bs-secondary-text-emphasis: rgb(13.8039215686%, 15.2156862745%, 17.2549019608%);--bs-success-text-emphasis: rgb(3.9215686275%, 21.1764705882%, 13.1764705882%);--bs-info-text-emphasis: rgb(2.0392156863%, 31.6862745098%, 37.6470588235%);--bs-warning-text-emphasis: rgb(40%, 30.2745098039%, 1.0980392157%);--bs-danger-text-emphasis: #480e14;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: rgb(83.8431372549%, 87.2156862745%, 93.1764705882%);--bs-secondary-bg-subtle: rgb(86.9019607843%, 87.6078431373%, 88.6274509804%);--bs-success-bg-subtle: rgb(81.9607843137%, 90.5882352941%, 86.5882352941%);--bs-info-bg-subtle: rgb(81.0196078431%, 95.8431372549%, 98.8235294118%);--bs-warning-bg-subtle: rgb(100%, 95.137254902%, 80.5490196078%);--bs-danger-bg-subtle: #f0d3d6;--bs-light-bg-subtle: rgb(98.6274509804%, 98.8235294118%, 99.0196078431%);--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: rgb(67.6862745098%, 74.431372549%, 86.3529411765%);--bs-secondary-border-subtle: rgb(73.8039215686%, 75.2156862745%, 77.2549019608%);--bs-success-border-subtle: rgb(63.9215686275%, 81.1764705882%, 73.1764705882%);--bs-info-border-subtle: rgb(62.0392156863%, 91.6862745098%, 97.6470588235%);--bs-warning-border-subtle: rgb(100%, 90.2745098039%, 61.0980392157%);--bs-danger-border-subtle: #e1a7ad;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size:0.875rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb: 33, 37, 41;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb: 33, 37, 41;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #315ca8;--bs-link-color-rgb: 49, 92, 168;--bs-link-decoration: underline;--bs-link-hover-color: rgb(15.3725490196%, 28.862745098%, 52.7058823529%);--bs-link-hover-color-rgb: 39, 74, 134;--bs-code-color: #d63384;--bs-highlight-color: #212529;--bs-highlight-bg: rgb(100%, 95.137254902%, 80.5490196078%);--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #dee2e6;--bs-border-color-translucent: rgba(0, 0, 0, 0.175);--bs-border-radius: 0.375rem;--bs-border-radius-sm: 0.25rem;--bs-border-radius-lg: 0.5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width: 0.25rem;--bs-focus-ring-opacity: 0.25;--bs-focus-ring-color: rgba(49, 92, 168, 0.25);--bs-form-valid-color: #198754;--bs-form-valid-border-color: #198754;--bs-form-invalid-color: #b42332;--bs-form-invalid-border-color: #b42332}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #212529;--bs-body-bg-rgb: 33, 37, 41;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255, 255, 255;--bs-secondary-color: rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb: 222, 226, 230;--bs-secondary-bg: #343a40;--bs-secondary-bg-rgb: 52, 58, 64;--bs-tertiary-color: rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb: 222, 226, 230;--bs-tertiary-bg: rgb(16.6666666667%, 18.6274509804%, 20.5882352941%);--bs-tertiary-bg-rgb: 43, 48, 53;--bs-primary-text-emphasis: rgb(51.5294117647%, 61.6470588235%, 79.5294117647%);--bs-secondary-text-emphasis: rgb(60.7058823529%, 62.8235294118%, 65.8823529412%);--bs-success-text-emphasis: rgb(45.8823529412%, 71.7647058824%, 59.7647058824%);--bs-info-text-emphasis: rgb(43.0588235294%, 87.5294117647%, 96.4705882353%);--bs-warning-text-emphasis: rgb(100%, 85.4117647059%, 41.6470588235%);--bs-danger-text-emphasis: #d27b84;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: rgb(3.8431372549%, 7.2156862745%, 13.1764705882%);--bs-secondary-bg-subtle: rgb(6.9019607843%, 7.6078431373%, 8.6274509804%);--bs-success-bg-subtle: rgb(1.9607843137%, 10.5882352941%, 6.5882352941%);--bs-info-bg-subtle: rgb(1.0196078431%, 15.8431372549%, 18.8235294118%);--bs-warning-bg-subtle: rgb(20%, 15.137254902%, 0.5490196078%);--bs-danger-bg-subtle: #24070a;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: rgb(11.5294117647%, 21.6470588235%, 39.5294117647%);--bs-secondary-border-subtle: rgb(20.7058823529%, 22.8235294118%, 25.8823529412%);--bs-success-border-subtle: rgb(5.8823529412%, 31.7647058824%, 19.7647058824%);--bs-info-border-subtle: rgb(3.0588235294%, 47.5294117647%, 56.4705882353%);--bs-warning-border-subtle: rgb(60%, 45.4117647059%, 1.6470588235%);--bs-danger-border-subtle: #6c151e;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: rgb(51.5294117647%, 61.6470588235%, 79.5294117647%);--bs-link-hover-color: rgb(61.2235294118%, 69.3176470588%, 83.6235294118%);--bs-link-color-rgb: 131, 157, 203;--bs-link-hover-color-rgb: 156, 177, 213;--bs-code-color: rgb(90.3529411765%, 52%, 71.0588235294%);--bs-highlight-color: #dee2e6;--bs-highlight-bg: rgb(40%, 30.2745098039%, 1.0980392157%);--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: rgb(45.8823529412%, 71.7647058824%, 59.7647058824%);--bs-form-valid-border-color: rgb(45.8823529412%, 71.7647058824%, 59.7647058824%);--bs-form-invalid-color: rgb(91.7647058824%, 52.4705882353%, 56.2352941176%);--bs-form-invalid-border-color: rgb(91.7647058824%, 52.4705882353%, 56.2352941176%)}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.4375rem + var(--bs-border-width));padding-bottom:calc(0.4375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + var(--bs-border-width));padding-bottom:calc(0.5rem + var(--bs-border-width));font-size:1.09375rem}.col-form-label-sm{padding-top:calc(0.25rem + var(--bs-border-width));padding-bottom:calc(0.25rem + var(--bs-border-width));font-size:0.765625rem}.form-text{margin-top:.25rem;font-size:0.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.4375rem .75rem;font-size:0.8125rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:rgb(59.6078431373%,68.0392156863%,82.9411764706%);outline:0;box-shadow:0 0 0 .25rem rgba(49,92,168,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::file-selector-button{padding:.4375rem .75rem;margin:-0.4375rem -0.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.4375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:0.765625rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.09375rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.875rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + 0.875rem + calc(var(--bs-border-width) * 2));padding:.4375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.4375rem 2.25rem .4375rem .75rem;font-size:0.8125rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:rgb(59.6078431373%,68.0392156863%,82.9411764706%);outline:0;box-shadow:0 0 0 .1875rem rgba(49,92,168,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.765625rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.09375rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.3125rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg: var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:rgb(59.6078431373%,68.0392156863%,82.9411764706%);outline:0;box-shadow:0 0 0 .25rem rgba(49,92,168,.25)}.form-check-input:checked{background-color:#315ca8;border-color:#315ca8}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#315ca8;border-color:#315ca8;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%2859.6078431373%, 68.0392156863%, 82.9411764706%%29'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.375rem;padding:0;appearance:none;background-color:rgba(0,0,0,0)}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(49,92,168,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(49,92,168,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;appearance:none;background-color:#315ca8;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:rgb(75.7647058824%,80.8235294118%,89.7647058824%)}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:var(--bs-secondary-bg);border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;appearance:none;background-color:#315ca8;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:rgb(75.7647058824%,80.8235294118%,89.7647058824%)}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:var(--bs-secondary-bg);border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;max-width:100%;height:100%;padding:1rem .75rem;overflow:hidden;color:rgba(var(--bs-body-color-rgb), 0.65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>textarea:focus~label::after,.form-floating>textarea:not(:placeholder-shown)~label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>textarea:disabled~label::after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.4375rem .75rem;font-size:0.8125rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.09375rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.765625rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1*var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.765625rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + 0.875rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.21875rem) center;background-size:calc(0.75em + 0.4375rem) calc(0.75em + 0.4375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .1875rem rgba(var(--bs-success-rgb), 0.2)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.875rem);background-position:top calc(0.375em + 0.21875rem) right calc(0.375em + 0.21875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.4375rem) calc(0.75em + 0.4375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .1875rem rgba(var(--bs-success-rgb), 0.2)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + 0.875rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .1875rem rgba(var(--bs-success-rgb), 0.2)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.765625rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + 0.875rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23b42332'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23b42332' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.21875rem) center;background-size:calc(0.75em + 0.4375rem) calc(0.75em + 0.4375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .1875rem rgba(var(--bs-danger-rgb), 0.2)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.875rem);background-position:top calc(0.375em + 0.21875rem) right calc(0.375em + 0.21875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23b42332'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23b42332' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.4375rem) calc(0.75em + 0.4375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .1875rem rgba(var(--bs-danger-rgb), 0.2)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + 0.875rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .1875rem rgba(var(--bs-danger-rgb), 0.2)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: 0.75rem;--bs-btn-padding-y: 0.4375rem;--bs-btn-font-family: ;--bs-btn-font-size:0.8125rem;--bs-btn-font-weight: 500;--bs-btn-line-height: 1.5;--bs-btn-color: var(--bs-body-color);--bs-btn-bg: transparent;--bs-btn-border-width: var(--bs-border-width);--bs-btn-border-color: transparent;--bs-btn-border-radius: var(--bs-border-radius);--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity: 0.65;--bs-btn-focus-box-shadow: 0 0 0 0.1875rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;white-space:nowrap;vertical-align:middle;cursor:pointer;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #315ca8;--bs-btn-border-color: #315ca8;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: rgb(16.3333333333%, 30.6666666667%, 56%);--bs-btn-hover-border-color: rgb(15.3725490196%, 28.862745098%, 52.7058823529%);--bs-btn-focus-shadow-rgb: 80, 116, 181;--bs-btn-active-color: #fff;--bs-btn-active-bg: rgb(15.3725490196%, 28.862745098%, 52.7058823529%);--bs-btn-active-border-color: rgb(14.4117647059%, 27.0588235294%, 49.4117647059%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #315ca8;--bs-btn-disabled-border-color: #315ca8}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #58616e;--bs-btn-border-color: #58616e;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: rgb(29.3333333333%, 32.3333333333%, 36.6666666667%);--bs-btn-hover-border-color: rgb(27.6078431373%, 30.431372549%, 34.5098039216%);--bs-btn-focus-shadow-rgb: 113, 121, 132;--bs-btn-active-color: #fff;--bs-btn-active-bg: rgb(27.6078431373%, 30.431372549%, 34.5098039216%);--bs-btn-active-border-color: rgb(25.8823529412%, 28.5294117647%, 32.3529411765%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #58616e;--bs-btn-disabled-border-color: #58616e}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #198754;--bs-btn-border-color: #198754;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: rgb(8.3333333333%, 45%, 28%);--bs-btn-hover-border-color: rgb(7.8431372549%, 42.3529411765%, 26.3529411765%);--bs-btn-focus-shadow-rgb: 60, 153, 110;--bs-btn-active-color: #fff;--bs-btn-active-bg: rgb(7.8431372549%, 42.3529411765%, 26.3529411765%);--bs-btn-active-border-color: rgb(7.3529411765%, 39.7058823529%, 24.7058823529%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #198754;--bs-btn-disabled-border-color: #198754}.btn-info{--bs-btn-color: #000;--bs-btn-bg: #0dcaf0;--bs-btn-border-color: #0dcaf0;--bs-btn-hover-color: #000;--bs-btn-hover-bg: rgb(19.3333333333%, 82.3333333333%, 95%);--bs-btn-hover-border-color: rgb(14.5882352941%, 81.2941176471%, 94.7058823529%);--bs-btn-focus-shadow-rgb: 11, 172, 204;--bs-btn-active-color: #000;--bs-btn-active-bg: rgb(24.0784313725%, 83.3725490196%, 95.2941176471%);--bs-btn-active-border-color: rgb(14.5882352941%, 81.2941176471%, 94.7058823529%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #0dcaf0;--bs-btn-disabled-border-color: #0dcaf0}.btn-warning{--bs-btn-color: #000;--bs-btn-bg: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: rgb(100%, 79.3333333333%, 17.3333333333%);--bs-btn-hover-border-color: rgb(100%, 78.1176470588%, 12.4705882353%);--bs-btn-focus-shadow-rgb: 217, 164, 6;--bs-btn-active-color: #000;--bs-btn-active-bg: rgb(100%, 80.5490196078%, 22.1960784314%);--bs-btn-active-border-color: rgb(100%, 78.1176470588%, 12.4705882353%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #ffc107;--bs-btn-disabled-border-color: #ffc107}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #b42332;--bs-btn-border-color: #b42332;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: rgb(60%, 11.6666666667%, 16.6666666667%);--bs-btn-hover-border-color: #901c28;--bs-btn-focus-shadow-rgb: 191, 68, 81;--bs-btn-active-color: #fff;--bs-btn-active-bg: #901c28;--bs-btn-active-border-color: rgb(52.9411764706%, 10.2941176471%, 14.7058823529%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #b42332;--bs-btn-disabled-border-color: #b42332}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: rgb(82.6666666667%, 83%, 83.3333333333%);--bs-btn-hover-border-color: rgb(77.8039215686%, 78.1176470588%, 78.431372549%);--bs-btn-focus-shadow-rgb: 211, 212, 213;--bs-btn-active-color: #000;--bs-btn-active-bg: rgb(77.8039215686%, 78.1176470588%, 78.431372549%);--bs-btn-active-border-color: rgb(72.9411764706%, 73.2352941176%, 73.5294117647%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #212529;--bs-btn-border-color: #212529;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: rgb(26%, 27.3333333333%, 28.6666666667%);--bs-btn-hover-border-color: rgb(21.6470588235%, 23.0588235294%, 24.4705882353%);--bs-btn-focus-shadow-rgb: 66, 70, 73;--bs-btn-active-color: #fff;--bs-btn-active-bg: rgb(30.3529411765%, 31.6078431373%, 32.862745098%);--bs-btn-active-border-color: rgb(21.6470588235%, 23.0588235294%, 24.4705882353%);--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #212529;--bs-btn-disabled-border-color: #212529}.btn-outline-primary{--bs-btn-color: #315ca8;--bs-btn-border-color: #315ca8;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #315ca8;--bs-btn-hover-border-color: #315ca8;--bs-btn-focus-shadow-rgb: 49, 92, 168;--bs-btn-active-color: #fff;--bs-btn-active-bg: #315ca8;--bs-btn-active-border-color: #315ca8;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #315ca8;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #315ca8;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #58616e;--bs-btn-border-color: #58616e;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #58616e;--bs-btn-hover-border-color: #58616e;--bs-btn-focus-shadow-rgb: 88, 97, 110;--bs-btn-active-color: #fff;--bs-btn-active-bg: #58616e;--bs-btn-active-border-color: #58616e;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #58616e;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #58616e;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #198754;--bs-btn-border-color: #198754;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #198754;--bs-btn-hover-border-color: #198754;--bs-btn-focus-shadow-rgb: 25, 135, 84;--bs-btn-active-color: #fff;--bs-btn-active-bg: #198754;--bs-btn-active-border-color: #198754;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #198754;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #198754;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #0dcaf0;--bs-btn-border-color: #0dcaf0;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #0dcaf0;--bs-btn-hover-border-color: #0dcaf0;--bs-btn-focus-shadow-rgb: 13, 202, 240;--bs-btn-active-color: #000;--bs-btn-active-bg: #0dcaf0;--bs-btn-active-border-color: #0dcaf0;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #0dcaf0;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #0dcaf0;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 255, 193, 7;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffc107;--bs-btn-active-border-color: #ffc107;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffc107;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ffc107;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #b42332;--bs-btn-border-color: #b42332;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #b42332;--bs-btn-hover-border-color: #b42332;--bs-btn-focus-shadow-rgb: 180, 35, 50;--bs-btn-active-color: #fff;--bs-btn-active-bg: #b42332;--bs-btn-active-border-color: #b42332;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #b42332;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #b42332;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248, 249, 250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #212529;--bs-btn-border-color: #212529;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #212529;--bs-btn-hover-border-color: #212529;--bs-btn-focus-shadow-rgb: 33, 37, 41;--bs-btn-active-color: #fff;--bs-btn-active-bg: #212529;--bs-btn-active-border-color: #212529;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #212529;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #212529;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: var(--bs-link-color);--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: var(--bs-link-hover-color);--bs-btn-hover-border-color: transparent;--bs-btn-active-color: var(--bs-link-hover-color);--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 80, 116, 181;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: 0.5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.09375rem;--bs-btn-border-radius: var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: 0.25rem;--bs-btn-padding-x: 0.5rem;--bs-btn-font-size:0.765625rem;--bs-btn-border-radius: var(--bs-border-radius-sm)}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(-1*var(--bs-border-width))}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(-1*var(--bs-border-width))}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}:root,[data-bs-theme=dark]{--bs-body-font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Apple SD Gothic Neo, Malgun Gothic, sans-serif;--bs-body-color: var(--text, #20242b);--bs-body-bg: var(--panel, #fff);--bs-secondary-color: var(--muted, #58616e);--bs-border-color: var(--line-2, #b9c1cc);--bs-secondary-bg: var(--panel-2, #eef0f3)}.btn{min-height:36px;display:inline-flex;align-items:center;justify-content:center;gap:.375rem}.btn[hidden]{display:none !important}.form-control,.form-select{min-height:38px;font-family:var(--bs-body-font-family)}.form-control::placeholder{color:var(--muted, #58616e);opacity:.85}.form-control:focus,.form-select:focus{border-color:var(--accent, #315ca8)}.btn-outline-secondary,.btn-ghost{--bs-btn-color: var(--text, #20242b);--bs-btn-border-color: var(--line, #dfe3e8);--bs-btn-hover-color: var(--text, #20242b);--bs-btn-hover-bg: var(--panel-2, #eef0f3);--bs-btn-hover-border-color: var(--line-2, #b9c1cc);--bs-btn-active-color: var(--text, #20242b);--bs-btn-active-bg: var(--panel-2, #eef0f3);--bs-btn-active-border-color: var(--line-2, #b9c1cc);--bs-btn-disabled-color: var(--muted, #58616e);--bs-btn-disabled-border-color: var(--line, #dfe3e8);--bs-btn-focus-shadow-rgb: 49, 92, 168}.btn-ghost{--bs-btn-border-color: transparent}[data-bs-theme=dark] .btn-outline-danger{--bs-btn-color: #ff9ba5;--bs-btn-border-color: #73404a;--bs-btn-hover-bg: #93313e;--bs-btn-hover-border-color: #93313e}.btn:focus-visible{outline:2px solid var(--ring, #315ca8);outline-offset:2px}@media(pointer: coarse){.btn,.form-control,.form-select{min-height:44px}}form[data-auto-apply] [hidden]{display:none !important}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const panel = document.querySelector("[data-update-status]");
|
|
2
|
+
|
|
3
|
+
if (panel?.dataset.active === "true") {
|
|
4
|
+
const messages = JSON.parse(panel.dataset.messages);
|
|
5
|
+
const stateLabel = panel.querySelector("[data-update-state]");
|
|
6
|
+
const errorLabel = panel.querySelector("[data-update-error]");
|
|
7
|
+
const versionLabel = document.querySelector("[data-update-version]");
|
|
8
|
+
|
|
9
|
+
async function refreshStatus() {
|
|
10
|
+
let retry = true;
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch("/admin/update/status", { credentials: "same-origin", cache: "no-store", headers: { Accept: "application/json" } });
|
|
13
|
+
if ([401, 403].includes(response.status)) {
|
|
14
|
+
stateLabel.textContent = messages.signIn;
|
|
15
|
+
panel.querySelector("[data-update-login]").hidden = false;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!response.ok) throw new Error("Status unavailable");
|
|
19
|
+
const status = await response.json();
|
|
20
|
+
if (status.currentVersion) versionLabel.textContent = status.currentVersion;
|
|
21
|
+
if (status.run) {
|
|
22
|
+
stateLabel.textContent = messages[status.run.state] || messages.running;
|
|
23
|
+
errorLabel.textContent = status.run.error || "";
|
|
24
|
+
retry = ["queued", "running"].includes(status.run.state);
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
stateLabel.textContent = messages.reconnect;
|
|
28
|
+
}
|
|
29
|
+
if (retry) window.setTimeout(refreshStatus, 3000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.setTimeout(refreshStatus, 1500);
|
|
33
|
+
}
|