plum-e2e 1.2.3 → 1.3.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/CLAUDE.md +201 -0
- package/README.md +237 -90
- package/backend/_scaffold/utils/browser.ts +5 -2
- package/backend/app.js +9 -1
- package/backend/config/scripts/generate-report.js +34 -73
- package/backend/config/scripts/run-tests.js +7 -3
- package/backend/constants/triggers.js +67 -0
- package/backend/lib/reportFilename.js +37 -0
- package/backend/lib/testChunker.js +73 -0
- package/backend/middleware/auth.js +32 -0
- package/backend/package.json +4 -2
- package/backend/prisma/migrations/20260616000000_add_runners_and_browser/migration.sql +26 -0
- package/backend/prisma/migrations/20260616000001_cron_runner_ids/migration.sql +6 -0
- package/backend/prisma/migrations/20260617000000_cron_enabled/migration.sql +1 -0
- package/backend/prisma/migrations/20260617000001_report_content/migration.sql +8 -0
- package/backend/prisma/schema.prisma +21 -1
- package/backend/routes/cron.routes.js +28 -0
- package/backend/routes/node.routes.js +121 -0
- package/backend/routes/reports.routes.js +23 -20
- package/backend/routes/runners.routes.js +83 -0
- package/backend/scripts/add-local-runner.js +120 -0
- package/backend/scripts/create-test.js +148 -0
- package/backend/server.js +16 -7
- package/backend/services/backupService.js +3 -30
- package/backend/services/cronService.js +220 -36
- package/backend/services/reportService.js +227 -55
- package/backend/services/runnerService.js +179 -0
- package/backend/websockets/socketHandler.js +162 -21
- package/bin/plum.js +191 -47
- package/docker-compose.node.yml +59 -0
- package/docker-compose.yml +2 -0
- package/frontend/package.json +1 -4
- package/frontend/src/app.css +20 -254
- package/frontend/src/app.html +1 -1
- package/frontend/src/lib/api/reports.js +17 -36
- package/frontend/src/lib/api/runners.js +61 -0
- package/frontend/src/lib/api/schedules.js +34 -5
- package/frontend/src/lib/api/settings.js +5 -5
- package/frontend/src/lib/api/tests.js +2 -19
- package/frontend/src/lib/components/icons/BrowserIcon.svelte +75 -0
- package/frontend/src/lib/components/layout/Nav.svelte +42 -47
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +913 -253
- package/frontend/src/lib/components/ui/Badge.svelte +6 -1
- package/frontend/src/lib/components/ui/ConfirmModal.svelte +98 -0
- package/frontend/{tailwind.config.js → src/lib/components/ui/EmptyState.svelte} +27 -8
- package/frontend/{postcss.config.js → src/lib/components/ui/Toast.svelte} +20 -7
- package/frontend/src/lib/constants.js +36 -0
- package/frontend/src/lib/stores/runner.js +23 -12
- package/frontend/src/lib/styles/global.css +176 -0
- package/frontend/src/lib/styles/reset.css +86 -0
- package/frontend/src/lib/styles/tokens.css +90 -0
- package/frontend/src/lib/utils/format.js +46 -0
- package/frontend/src/routes/+page.svelte +16 -35
- package/frontend/src/routes/reports/+page.svelte +84 -167
- package/frontend/src/routes/reports/{[slug] → [id]}/+page.svelte +304 -76
- package/frontend/src/routes/reports/live/+page.svelte +704 -0
- package/frontend/src/routes/scheduled-tests/+page.svelte +328 -88
- package/frontend/src/routes/settings/+page.svelte +774 -127
- package/frontend/static/favicon-32x32.png +0 -0
- package/frontend/static/favicon.ico +0 -0
- package/package.json +2 -2
- package/frontend/static/favicon.png +0 -0
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
18
|
<script>
|
|
19
|
-
/** @type {'pass' | 'fail' | 'tag' | 'schedule' | 'neutral'} */
|
|
19
|
+
/** @type {'pass' | 'fail' | 'tag' | 'schedule' | 'neutral' | 'node'} */
|
|
20
20
|
export let variant = 'neutral';
|
|
21
21
|
</script>
|
|
22
22
|
|
|
@@ -60,4 +60,9 @@
|
|
|
60
60
|
color: var(--text-muted);
|
|
61
61
|
border: 1px solid var(--border);
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
.node {
|
|
65
|
+
background: var(--node-soft);
|
|
66
|
+
color: var(--node);
|
|
67
|
+
}
|
|
63
68
|
</style>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { createEventDispatcher } from 'svelte';
|
|
20
|
+
import Modal from './Modal.svelte';
|
|
21
|
+
|
|
22
|
+
export let open = false;
|
|
23
|
+
export let title = 'Confirm';
|
|
24
|
+
export let confirmLabel = 'Delete';
|
|
25
|
+
export let loading = false;
|
|
26
|
+
|
|
27
|
+
const dispatch = createEventDispatcher();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<Modal bind:open {title}>
|
|
31
|
+
<div class="body">
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
<div class="actions">
|
|
35
|
+
<button class="btn-danger" on:click={() => dispatch('confirm')} disabled={loading}>
|
|
36
|
+
{loading ? 'Working…' : confirmLabel}
|
|
37
|
+
</button>
|
|
38
|
+
<button class="btn-cancel" on:click={() => (open = false)} disabled={loading}>Cancel</button>
|
|
39
|
+
</div>
|
|
40
|
+
</Modal>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.body {
|
|
44
|
+
font-size: 0.9375rem;
|
|
45
|
+
color: var(--text-muted);
|
|
46
|
+
line-height: 1.6;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.body :global(strong) {
|
|
50
|
+
color: var(--text);
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.actions {
|
|
55
|
+
display: flex;
|
|
56
|
+
gap: 0.625rem;
|
|
57
|
+
padding-top: 0.25rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.btn-danger {
|
|
61
|
+
height: 34px;
|
|
62
|
+
padding: 0 1rem;
|
|
63
|
+
font-size: 0.8125rem;
|
|
64
|
+
font-family: inherit;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
background: var(--fail);
|
|
67
|
+
border: 1px solid var(--fail);
|
|
68
|
+
border-radius: var(--radius-sm);
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
color: #fff;
|
|
71
|
+
transition: opacity var(--duration-fast);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.btn-danger:hover:not(:disabled) {
|
|
75
|
+
opacity: 0.85;
|
|
76
|
+
}
|
|
77
|
+
.btn-danger:disabled {
|
|
78
|
+
opacity: 0.5;
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.btn-cancel {
|
|
83
|
+
height: 34px;
|
|
84
|
+
padding: 0 1rem;
|
|
85
|
+
font-size: 0.8125rem;
|
|
86
|
+
font-family: inherit;
|
|
87
|
+
background: var(--bg-elevated);
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
border-radius: var(--radius-sm);
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
color: var(--text);
|
|
92
|
+
transition: background var(--duration-fast);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.btn-cancel:hover {
|
|
96
|
+
background: var(--bg-subtle);
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
2
|
* This file is part of Plum.
|
|
3
3
|
*
|
|
4
4
|
* Plum is free software: you can redistribute it and/or modify
|
|
@@ -13,11 +13,30 @@
|
|
|
13
13
|
*
|
|
14
14
|
* You should have received a copy of the GNU General Public License
|
|
15
15
|
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
-
|
|
16
|
+
-->
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
<script>
|
|
19
|
+
export let message = 'Nothing here yet.';
|
|
20
|
+
/** Optional extra padding class: 'sm' | 'md' (default) | 'lg' */
|
|
21
|
+
export let size = 'md';
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<p class="empty {size}"><slot>{message}</slot></p>
|
|
25
|
+
|
|
26
|
+
<style>
|
|
27
|
+
.empty {
|
|
28
|
+
color: var(--text-muted);
|
|
29
|
+
font-size: 0.9375rem;
|
|
30
|
+
text-align: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sm {
|
|
34
|
+
padding: 1.5rem 0;
|
|
35
|
+
}
|
|
36
|
+
.md {
|
|
37
|
+
padding: 3rem 0;
|
|
38
|
+
}
|
|
39
|
+
.lg {
|
|
40
|
+
padding: 5rem 0;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
2
|
* This file is part of Plum.
|
|
3
3
|
*
|
|
4
4
|
* Plum is free software: you can redistribute it and/or modify
|
|
@@ -13,11 +13,24 @@
|
|
|
13
13
|
*
|
|
14
14
|
* You should have received a copy of the GNU General Public License
|
|
15
15
|
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
-
|
|
16
|
+
-->
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
<script>
|
|
19
|
+
import { fly } from 'svelte/transition';
|
|
20
|
+
|
|
21
|
+
/** @type {{ type: 'success' | 'error', message: string } | null} */
|
|
22
|
+
export let toast = null;
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if toast}
|
|
26
|
+
<div class="toast alert alert-{toast.type}" transition:fly={{ y: -8, duration: 240 }}>
|
|
27
|
+
{toast.message}
|
|
28
|
+
</div>
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.toast {
|
|
33
|
+
margin-bottom: 1.25rem;
|
|
34
|
+
border-radius: var(--radius-md);
|
|
22
35
|
}
|
|
23
|
-
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3001';
|
|
19
|
+
|
|
20
|
+
export const BROWSERS = [
|
|
21
|
+
{ id: 'chromium', label: 'Chrome' },
|
|
22
|
+
{ id: 'firefox', label: 'Firefox' },
|
|
23
|
+
{ id: 'webkit', label: 'WebKit' }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const TRIGGER_TYPES = Object.freeze({
|
|
27
|
+
MANUAL: 'manual-trigger',
|
|
28
|
+
CLI: 'command-line-trigger'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const WORKER_OPTIONS = [1, 2, 4, 8];
|
|
32
|
+
|
|
33
|
+
export const REPORTS_PER_PAGE = 15;
|
|
34
|
+
|
|
35
|
+
export const COPY_TIMEOUT_MS = 1400;
|
|
36
|
+
export const TOAST_TIMEOUT_MS = 4000;
|
|
@@ -20,25 +20,28 @@ import { writable, get } from 'svelte/store';
|
|
|
20
20
|
export const socket = writable(null);
|
|
21
21
|
|
|
22
22
|
export const runnerState = writable({
|
|
23
|
-
output: '
|
|
23
|
+
output: '',
|
|
24
24
|
running: false,
|
|
25
25
|
testCompleted: false,
|
|
26
|
-
|
|
26
|
+
latestReportId: null, // number | null — set after test finishes
|
|
27
27
|
status: 'idle', // 'idle' | 'running' | 'pass' | 'fail'
|
|
28
|
-
lastRunId: ''
|
|
28
|
+
lastRunId: '',
|
|
29
|
+
lanes: [], // [{ id, name, testCount, status, logs }] multi-runner only
|
|
30
|
+
currentRun: null // { tag, workers, browser, runners } — set while running
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
export const runnerConfig = writable({
|
|
32
34
|
workers: 1,
|
|
33
|
-
testID: ''
|
|
35
|
+
testID: '',
|
|
36
|
+
browser: 'chromium',
|
|
37
|
+
selectedRunners: ['built-in']
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
export const panelExpanded = writable(
|
|
40
|
+
export const panelExpanded = writable(false);
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
export const testsVersion = writable(0);
|
|
42
|
+
export const builtInEnabled = writable(true);
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
export const testsVersion = writable(0);
|
|
42
45
|
export const reportsVersion = writable(0);
|
|
43
46
|
|
|
44
47
|
// Map of taskName → true for every cron job currently executing
|
|
@@ -48,17 +51,25 @@ export function triggerRun(id) {
|
|
|
48
51
|
const s = get(socket);
|
|
49
52
|
if (!s) return;
|
|
50
53
|
|
|
51
|
-
const { workers, testID } = get(runnerConfig);
|
|
54
|
+
const { workers, testID, browser, selectedRunners } = get(runnerConfig);
|
|
52
55
|
const runId = (id !== undefined ? id : testID).trim().replace(/\sOR\s/gi, (m) => m.toLowerCase());
|
|
53
56
|
|
|
54
57
|
runnerState.set({
|
|
55
58
|
output: `Running: ${runId || '(all tests)'}\n`,
|
|
56
59
|
running: true,
|
|
57
60
|
testCompleted: false,
|
|
58
|
-
|
|
61
|
+
latestReportId: null,
|
|
59
62
|
status: 'running',
|
|
60
|
-
lastRunId: runId
|
|
63
|
+
lastRunId: runId,
|
|
64
|
+
lanes: [],
|
|
65
|
+
currentRun: { tag: runId, workers, browser, runners: selectedRunners }
|
|
61
66
|
});
|
|
62
67
|
panelExpanded.set(true);
|
|
63
|
-
|
|
68
|
+
|
|
69
|
+
s.emit('run-test', { tag: runId, workers, browser, runners: selectedRunners });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function cancelRun() {
|
|
73
|
+
const s = get(socket);
|
|
74
|
+
if (s) s.emit('cancel-test');
|
|
64
75
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file is part of Plum.
|
|
3
|
+
|
|
4
|
+
Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
Plum is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
/*
|
|
18
|
+
* This file is part of Plum.
|
|
19
|
+
*
|
|
20
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
21
|
+
* it under the terms of the GNU General Public License as published by
|
|
22
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
23
|
+
* (at your option) any later version.
|
|
24
|
+
*
|
|
25
|
+
* Plum is distributed in the hope that it will be useful,
|
|
26
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
27
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
28
|
+
* GNU General Public License for more details.
|
|
29
|
+
*
|
|
30
|
+
* You should have received a copy of the GNU General Public License
|
|
31
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/* ── Form fields ────────────────────────────────────────────────────────── */
|
|
35
|
+
|
|
36
|
+
.field {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 0.375rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.field-label {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
font-size: 0.8125rem;
|
|
46
|
+
font-weight: 400;
|
|
47
|
+
color: var(--text-muted);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.field-hint {
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
color: var(--text-muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.field-input {
|
|
56
|
+
width: 100%;
|
|
57
|
+
padding: 0.6rem 0.875rem;
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: var(--radius-md);
|
|
60
|
+
background: var(--bg-subtle);
|
|
61
|
+
color: var(--text);
|
|
62
|
+
font-family: var(--font-body);
|
|
63
|
+
font-size: 0.875rem;
|
|
64
|
+
font-weight: 300;
|
|
65
|
+
outline: none;
|
|
66
|
+
appearance: none;
|
|
67
|
+
transition:
|
|
68
|
+
border-color var(--duration-fast),
|
|
69
|
+
box-shadow var(--duration-fast);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.field-input:focus {
|
|
73
|
+
border-color: var(--accent);
|
|
74
|
+
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.field-input:disabled {
|
|
78
|
+
opacity: 0.5;
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.field-input::placeholder {
|
|
83
|
+
color: var(--text-muted);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ── Data table ─────────────────────────────────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
.data-table {
|
|
89
|
+
width: 100%;
|
|
90
|
+
border-collapse: collapse;
|
|
91
|
+
font-size: 0.875rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.data-table th {
|
|
95
|
+
text-align: left;
|
|
96
|
+
padding: 0.5rem 1rem;
|
|
97
|
+
font-family: var(--font-body);
|
|
98
|
+
font-weight: 500;
|
|
99
|
+
font-size: 0.7rem;
|
|
100
|
+
letter-spacing: 0.07em;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
color: var(--text-muted);
|
|
103
|
+
border-bottom: 1px solid var(--border);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.data-table td {
|
|
107
|
+
padding: 0.875rem 1rem;
|
|
108
|
+
border-bottom: 1px solid var(--border);
|
|
109
|
+
color: var(--text);
|
|
110
|
+
vertical-align: middle;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.data-table tbody tr:last-child td {
|
|
114
|
+
border-bottom: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ── Card ───────────────────────────────────────────────────────────────── */
|
|
118
|
+
|
|
119
|
+
.card {
|
|
120
|
+
background: var(--bg-elevated);
|
|
121
|
+
border: 1px solid var(--border);
|
|
122
|
+
border-radius: var(--radius-lg);
|
|
123
|
+
padding: 1.5rem;
|
|
124
|
+
transition:
|
|
125
|
+
background var(--duration-base) var(--ease-out),
|
|
126
|
+
border-color var(--duration-base) var(--ease-out);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.card-title {
|
|
130
|
+
font-family: var(--font-display);
|
|
131
|
+
font-size: 1.25rem;
|
|
132
|
+
font-weight: 400;
|
|
133
|
+
color: var(--text);
|
|
134
|
+
margin-bottom: 0.25rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.card-subtitle {
|
|
138
|
+
font-size: 0.8125rem;
|
|
139
|
+
color: var(--text-muted);
|
|
140
|
+
margin-bottom: 1.25rem;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ── Alert ──────────────────────────────────────────────────────────────── */
|
|
144
|
+
|
|
145
|
+
.alert {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 0.625rem;
|
|
149
|
+
padding: 0.75rem 1rem;
|
|
150
|
+
border-radius: var(--radius-md);
|
|
151
|
+
font-size: 0.875rem;
|
|
152
|
+
font-weight: 400;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.alert-success {
|
|
156
|
+
background: var(--pass-soft);
|
|
157
|
+
color: var(--pass);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.alert-error {
|
|
161
|
+
background: var(--fail-soft);
|
|
162
|
+
color: var(--fail);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Animations ─────────────────────────────────────────────────────────── */
|
|
166
|
+
|
|
167
|
+
@keyframes fadeUp {
|
|
168
|
+
from {
|
|
169
|
+
opacity: 0;
|
|
170
|
+
transform: translateY(10px);
|
|
171
|
+
}
|
|
172
|
+
to {
|
|
173
|
+
opacity: 1;
|
|
174
|
+
transform: translateY(0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file is part of Plum.
|
|
3
|
+
|
|
4
|
+
Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
Plum is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
/*
|
|
18
|
+
* This file is part of Plum.
|
|
19
|
+
*
|
|
20
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
21
|
+
* it under the terms of the GNU General Public License as published by
|
|
22
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
23
|
+
* (at your option) any later version.
|
|
24
|
+
*
|
|
25
|
+
* Plum is distributed in the hope that it will be useful,
|
|
26
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
27
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
28
|
+
* GNU General Public License for more details.
|
|
29
|
+
*
|
|
30
|
+
* You should have received a copy of the GNU General Public License
|
|
31
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/* ── Minimal modern reset ──────────────────────────────────────────────── */
|
|
35
|
+
|
|
36
|
+
*,
|
|
37
|
+
*::before,
|
|
38
|
+
*::after {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
html {
|
|
45
|
+
scroll-behavior: smooth;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
body {
|
|
49
|
+
font-family: var(--font-body);
|
|
50
|
+
font-weight: 300;
|
|
51
|
+
font-size: 1rem;
|
|
52
|
+
line-height: 1.65;
|
|
53
|
+
background-color: var(--bg);
|
|
54
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.07'/%3E%3C/svg%3E");
|
|
55
|
+
background-size: 200px 200px;
|
|
56
|
+
color: var(--text);
|
|
57
|
+
-webkit-font-smoothing: antialiased;
|
|
58
|
+
transition:
|
|
59
|
+
background-color var(--duration-base) var(--ease-out),
|
|
60
|
+
color var(--duration-base) var(--ease-out);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
h1 {
|
|
64
|
+
font-family: var(--font-display);
|
|
65
|
+
font-weight: 400;
|
|
66
|
+
line-height: 1.1;
|
|
67
|
+
letter-spacing: -0.015em;
|
|
68
|
+
color: var(--text);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
h2,
|
|
72
|
+
h3 {
|
|
73
|
+
font-family: var(--font-display);
|
|
74
|
+
font-weight: 400;
|
|
75
|
+
line-height: 1.2;
|
|
76
|
+
color: var(--text);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
a {
|
|
80
|
+
color: inherit;
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
button {
|
|
85
|
+
font-family: var(--font-body);
|
|
86
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file is part of Plum.
|
|
3
|
+
|
|
4
|
+
Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
Plum is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
/*
|
|
18
|
+
* This file is part of Plum.
|
|
19
|
+
*
|
|
20
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
21
|
+
* it under the terms of the GNU General Public License as published by
|
|
22
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
23
|
+
* (at your option) any later version.
|
|
24
|
+
*
|
|
25
|
+
* Plum is distributed in the hope that it will be useful,
|
|
26
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
27
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
28
|
+
* GNU General Public License for more details.
|
|
29
|
+
*
|
|
30
|
+
* You should have received a copy of the GNU General Public License
|
|
31
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/* ── Design tokens (light) ─────────────────────────────────────────────── */
|
|
35
|
+
|
|
36
|
+
:root {
|
|
37
|
+
--bg: #fdfcfb;
|
|
38
|
+
--bg-subtle: #f7f6f4;
|
|
39
|
+
--bg-elevated: #ffffff;
|
|
40
|
+
--border: #e9e5df;
|
|
41
|
+
--text: #18160f;
|
|
42
|
+
--text-muted: #9c9185;
|
|
43
|
+
--accent: #5c1b87;
|
|
44
|
+
--accent-soft: #f3eaff;
|
|
45
|
+
--pass: #16a34a;
|
|
46
|
+
--pass-soft: #dcfce7;
|
|
47
|
+
--fail: #dc2626;
|
|
48
|
+
--fail-soft: #fee2e2;
|
|
49
|
+
--warn: #d97706;
|
|
50
|
+
--warn-soft: #fef3c7;
|
|
51
|
+
--node: #0284c7;
|
|
52
|
+
--node-soft: #e0f2fe;
|
|
53
|
+
--terminal-bg: #0d0c08;
|
|
54
|
+
--terminal-text: #d6d0c8;
|
|
55
|
+
|
|
56
|
+
--font-display: 'Playfair Display', Georgia, serif;
|
|
57
|
+
--font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
|
|
58
|
+
|
|
59
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
60
|
+
--duration-fast: 140ms;
|
|
61
|
+
--duration-base: 240ms;
|
|
62
|
+
--duration-slow: 400ms;
|
|
63
|
+
|
|
64
|
+
--radius-sm: 6px;
|
|
65
|
+
--radius-md: 10px;
|
|
66
|
+
--radius-lg: 16px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── Design tokens (dark) ──────────────────────────────────────────────── */
|
|
70
|
+
|
|
71
|
+
[data-theme='dark'] {
|
|
72
|
+
--bg: #101009;
|
|
73
|
+
--bg-subtle: #1a1910;
|
|
74
|
+
--bg-elevated: #201f14;
|
|
75
|
+
--border: #2e2c1e;
|
|
76
|
+
--text: #f0ede6;
|
|
77
|
+
--text-muted: #706a5e;
|
|
78
|
+
--accent: #d0a5f5;
|
|
79
|
+
--accent-soft: #1b0d30;
|
|
80
|
+
--pass: #22c55e;
|
|
81
|
+
--pass-soft: #0d2118;
|
|
82
|
+
--fail: #f87171;
|
|
83
|
+
--fail-soft: #2d1010;
|
|
84
|
+
--warn: #fbbf24;
|
|
85
|
+
--warn-soft: #2a1f06;
|
|
86
|
+
--node: #38bdf8;
|
|
87
|
+
--node-soft: #082032;
|
|
88
|
+
--terminal-bg: #080807;
|
|
89
|
+
--terminal-text: #cbc5bd;
|
|
90
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { TRIGGER_TYPES } from '$lib/constants';
|
|
19
|
+
|
|
20
|
+
const NON_SCHEDULED = new Set([TRIGGER_TYPES.MANUAL, TRIGGER_TYPES.CLI, 'undefined']);
|
|
21
|
+
|
|
22
|
+
export function isScheduled(type) {
|
|
23
|
+
return !!type && !NON_SCHEDULED.has(type);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function triggerLabel(type) {
|
|
27
|
+
if (type === TRIGGER_TYPES.MANUAL) return 'Manual';
|
|
28
|
+
if (type === TRIGGER_TYPES.CLI || type === 'undefined') return 'CLI';
|
|
29
|
+
return 'Scheduled';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function triggerVariant(type) {
|
|
33
|
+
if (type === TRIGGER_TYPES.MANUAL) return 'tag';
|
|
34
|
+
if (type === TRIGGER_TYPES.CLI || type === 'undefined') return 'neutral';
|
|
35
|
+
return 'schedule';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Returns an inline style string for staggered fadeUp animations. */
|
|
39
|
+
export function stagger(i, stepMs = 45) {
|
|
40
|
+
return `animation-delay: ${i * stepMs}ms`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function fmtDuration(ms) {
|
|
44
|
+
if (ms >= 1000) return (ms / 1000).toFixed(2) + 's';
|
|
45
|
+
return ms + 'ms';
|
|
46
|
+
}
|