@alliance-droid/status-feedback-system 0.1.0 → 0.2.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/dist/api.d.ts +94 -0
- package/dist/api.js +100 -0
- package/dist/components/FeedbackBoard.svelte +842 -0
- package/dist/components/FeedbackBoard.svelte.d.ts +19 -0
- package/dist/components/FeedbackButton.svelte +3 -3
- package/dist/components/FeedbackButton.svelte.d.ts +1 -1
- package/dist/components/FeedbackForm.svelte +3 -3
- package/dist/components/FeedbackForm.svelte.d.ts +1 -1
- package/dist/components/StatusBanner.svelte +36 -3
- package/dist/components/StatusBanner.svelte.d.ts +7 -1
- package/dist/components/StatusPage.svelte +150 -83
- package/dist/components/StatusPage.svelte.d.ts +10 -3
- package/dist/index.d.ts +7 -9
- package/dist/index.js +6 -8
- package/dist/types.d.ts +13 -6
- package/package.json +2 -13
- package/dist/components/admin/AdminDashboard.svelte +0 -274
- package/dist/components/admin/AdminDashboard.svelte.d.ts +0 -9
- package/dist/components/admin/ApiKeyManager.svelte +0 -68
- package/dist/components/admin/ApiKeyManager.svelte.d.ts +0 -7
- package/dist/components/admin/FeedbackList.svelte +0 -160
- package/dist/components/admin/FeedbackList.svelte.d.ts +0 -17
- package/dist/components/admin/IncidentForm.svelte +0 -135
- package/dist/components/admin/IncidentForm.svelte.d.ts +0 -14
- package/dist/components/admin/IncidentManager.svelte +0 -153
- package/dist/components/admin/IncidentManager.svelte.d.ts +0 -13
- package/dist/components/admin/ProjectForm.svelte +0 -106
- package/dist/components/admin/ProjectForm.svelte.d.ts +0 -13
- package/dist/components/admin/ProjectList.svelte +0 -57
- package/dist/components/admin/ProjectList.svelte.d.ts +0 -10
- package/dist/components/admin/StatusControl.svelte +0 -62
- package/dist/components/admin/StatusControl.svelte.d.ts +0 -8
- package/dist/components/admin/SystemManager.svelte +0 -119
- package/dist/components/admin/SystemManager.svelte.d.ts +0 -13
- package/dist/components/admin/index.d.ts +0 -9
- package/dist/components/admin/index.js +0 -9
- package/dist/server/db.d.ts +0 -17
- package/dist/server/db.js +0 -68
- package/dist/server/feedback.d.ts +0 -27
- package/dist/server/feedback.js +0 -124
- package/dist/server/handlers.d.ts +0 -41
- package/dist/server/handlers.js +0 -251
- package/dist/server/incidents.d.ts +0 -27
- package/dist/server/incidents.js +0 -180
- package/dist/server/index.d.ts +0 -101
- package/dist/server/index.js +0 -71
- package/dist/server/projects.d.ts +0 -45
- package/dist/server/projects.js +0 -156
- package/dist/server/schema.d.ts +0 -9
- package/dist/server/schema.js +0 -94
- package/dist/server/systems.d.ts +0 -20
- package/dist/server/systems.js +0 -146
- package/dist/server/types.d.ts +0 -4
- package/dist/server/types.js +0 -4
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Incident, IncidentStatus } from '../../types.js';
|
|
3
|
-
import IncidentTimeline from '../shared/IncidentTimeline.svelte';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
incidents: Incident[];
|
|
7
|
-
onAddUpdate?: (incidentId: string, data: { status: IncidentStatus; message: string }) => Promise<void>;
|
|
8
|
-
onDelete?: (incidentId: string) => Promise<void>;
|
|
9
|
-
onCreate?: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { incidents, onAddUpdate, onDelete, onCreate }: Props = $props();
|
|
13
|
-
|
|
14
|
-
let expandedId = $state<string | null>(null);
|
|
15
|
-
let updateMessage = $state('');
|
|
16
|
-
let updateStatus = $state<IncidentStatus>('investigating');
|
|
17
|
-
let submitting = $state(false);
|
|
18
|
-
|
|
19
|
-
const statusOptions: { value: IncidentStatus; label: string }[] = [
|
|
20
|
-
{ value: 'investigating', label: 'Investigating' },
|
|
21
|
-
{ value: 'identified', label: 'Identified' },
|
|
22
|
-
{ value: 'monitoring', label: 'Monitoring' },
|
|
23
|
-
{ value: 'resolved', label: 'Resolved' },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
function toggleExpand(id: string) {
|
|
27
|
-
expandedId = expandedId === id ? null : id;
|
|
28
|
-
updateMessage = '';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function handleAddUpdate(incidentId: string) {
|
|
32
|
-
if (!updateMessage.trim()) return;
|
|
33
|
-
submitting = true;
|
|
34
|
-
try {
|
|
35
|
-
await onAddUpdate?.(incidentId, { status: updateStatus, message: updateMessage.trim() });
|
|
36
|
-
updateMessage = '';
|
|
37
|
-
} finally {
|
|
38
|
-
submitting = false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function handleDelete(id: string, title: string) {
|
|
43
|
-
if (!confirm(`Delete incident "${title}"?`)) return;
|
|
44
|
-
await onDelete?.(id);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function formatDate(timestamp: number): string {
|
|
48
|
-
return new Date(timestamp).toLocaleString();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const severityColors: Record<string, string> = {
|
|
52
|
-
minor: 'text-yellow-500 bg-yellow-500/10',
|
|
53
|
-
major: 'text-orange-500 bg-orange-500/10',
|
|
54
|
-
critical: 'text-red-500 bg-red-500/10',
|
|
55
|
-
};
|
|
56
|
-
</script>
|
|
57
|
-
|
|
58
|
-
<div class="space-y-4">
|
|
59
|
-
<div class="flex items-center justify-between">
|
|
60
|
-
<h3 class="text-base font-semibold text-foreground">
|
|
61
|
-
<i class="fa-solid fa-triangle-exclamation mr-1.5 text-foreground-tertiary"></i>
|
|
62
|
-
Incidents
|
|
63
|
-
</h3>
|
|
64
|
-
{#if onCreate}
|
|
65
|
-
<button
|
|
66
|
-
onclick={onCreate}
|
|
67
|
-
class="rounded-md bg-red-500 px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-red-600"
|
|
68
|
-
>
|
|
69
|
-
<i class="fa-solid fa-plus mr-1"></i>
|
|
70
|
-
Report Incident
|
|
71
|
-
</button>
|
|
72
|
-
{/if}
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{#if incidents.length === 0}
|
|
76
|
-
<div class="rounded-lg border border-border bg-surface p-6 text-center">
|
|
77
|
-
<i class="fa-solid fa-circle-check mb-2 text-xl text-green-500"></i>
|
|
78
|
-
<p class="text-sm text-foreground-secondary">No incidents</p>
|
|
79
|
-
</div>
|
|
80
|
-
{:else}
|
|
81
|
-
{#each incidents as incident}
|
|
82
|
-
<div class="rounded-lg border border-border bg-surface">
|
|
83
|
-
<button
|
|
84
|
-
onclick={() => toggleExpand(incident.id)}
|
|
85
|
-
class="flex w-full items-center justify-between p-3 text-left"
|
|
86
|
-
>
|
|
87
|
-
<div>
|
|
88
|
-
<div class="flex items-center gap-2">
|
|
89
|
-
<span class="text-sm font-semibold text-foreground">{incident.title}</span>
|
|
90
|
-
<span class="rounded px-1.5 py-0.5 text-xs capitalize {severityColors[incident.severity] || ''}">
|
|
91
|
-
{incident.severity}
|
|
92
|
-
</span>
|
|
93
|
-
<span class="rounded px-1.5 py-0.5 text-xs capitalize bg-surface-hover text-foreground-secondary">
|
|
94
|
-
{incident.status}
|
|
95
|
-
</span>
|
|
96
|
-
</div>
|
|
97
|
-
<p class="mt-0.5 text-xs text-foreground-tertiary">{formatDate(incident.createdAt)}</p>
|
|
98
|
-
</div>
|
|
99
|
-
<i class="fa-solid {expandedId === incident.id ? 'fa-chevron-up' : 'fa-chevron-down'} text-xs text-foreground-tertiary"></i>
|
|
100
|
-
</button>
|
|
101
|
-
|
|
102
|
-
{#if expandedId === incident.id}
|
|
103
|
-
<div class="border-t border-border p-3 space-y-4">
|
|
104
|
-
{#if incident.updates && incident.updates.length > 0}
|
|
105
|
-
<IncidentTimeline updates={incident.updates} />
|
|
106
|
-
{/if}
|
|
107
|
-
|
|
108
|
-
{#if incident.status !== 'resolved'}
|
|
109
|
-
<div class="rounded-lg border border-border bg-background p-3 space-y-3">
|
|
110
|
-
<div class="flex gap-2">
|
|
111
|
-
{#each statusOptions as opt}
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
onclick={() => (updateStatus = opt.value)}
|
|
115
|
-
class="rounded px-2 py-1 text-xs transition-colors {updateStatus === opt.value
|
|
116
|
-
? 'bg-primary text-primary-foreground'
|
|
117
|
-
: 'border border-border text-foreground-secondary hover:bg-surface-hover'}"
|
|
118
|
-
>
|
|
119
|
-
{opt.label}
|
|
120
|
-
</button>
|
|
121
|
-
{/each}
|
|
122
|
-
</div>
|
|
123
|
-
<textarea
|
|
124
|
-
bind:value={updateMessage}
|
|
125
|
-
placeholder="Update message..."
|
|
126
|
-
rows="2"
|
|
127
|
-
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
|
|
128
|
-
></textarea>
|
|
129
|
-
<button
|
|
130
|
-
onclick={() => handleAddUpdate(incident.id)}
|
|
131
|
-
disabled={submitting || !updateMessage.trim()}
|
|
132
|
-
class="rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground disabled:opacity-50"
|
|
133
|
-
>
|
|
134
|
-
{submitting ? 'Posting...' : 'Post Update'}
|
|
135
|
-
</button>
|
|
136
|
-
</div>
|
|
137
|
-
{/if}
|
|
138
|
-
|
|
139
|
-
<div class="flex justify-end">
|
|
140
|
-
<button
|
|
141
|
-
onclick={() => handleDelete(incident.id, incident.title)}
|
|
142
|
-
class="text-xs text-red-500 transition-colors hover:text-red-400"
|
|
143
|
-
>
|
|
144
|
-
<i class="fa-solid fa-trash mr-1"></i>
|
|
145
|
-
Delete Incident
|
|
146
|
-
</button>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
{/if}
|
|
150
|
-
</div>
|
|
151
|
-
{/each}
|
|
152
|
-
{/if}
|
|
153
|
-
</div>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Incident, IncidentStatus } from '../../types.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
incidents: Incident[];
|
|
4
|
-
onAddUpdate?: (incidentId: string, data: {
|
|
5
|
-
status: IncidentStatus;
|
|
6
|
-
message: string;
|
|
7
|
-
}) => Promise<void>;
|
|
8
|
-
onDelete?: (incidentId: string) => Promise<void>;
|
|
9
|
-
onCreate?: () => void;
|
|
10
|
-
}
|
|
11
|
-
declare const IncidentManager: import("svelte").Component<Props, {}, "">;
|
|
12
|
-
type IncidentManager = ReturnType<typeof IncidentManager>;
|
|
13
|
-
export default IncidentManager;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Project } from '../../types.js';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
project?: Project;
|
|
6
|
-
onSave?: (data: { name: string; description: string; allowedOrigins: string[] }) => Promise<void>;
|
|
7
|
-
onCancel?: () => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { project, onSave, onCancel }: Props = $props();
|
|
11
|
-
|
|
12
|
-
let name = $state(project?.name ?? '');
|
|
13
|
-
let description = $state(project?.description ?? '');
|
|
14
|
-
let originsText = $state(project?.allowedOrigins?.join('\n') ?? '');
|
|
15
|
-
let saving = $state(false);
|
|
16
|
-
let errorMsg = $state('');
|
|
17
|
-
|
|
18
|
-
async function handleSubmit(e: Event) {
|
|
19
|
-
e.preventDefault();
|
|
20
|
-
if (!name.trim()) return;
|
|
21
|
-
|
|
22
|
-
saving = true;
|
|
23
|
-
errorMsg = '';
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const origins = originsText
|
|
27
|
-
.split('\n')
|
|
28
|
-
.map((o) => o.trim())
|
|
29
|
-
.filter(Boolean);
|
|
30
|
-
await onSave?.({ name: name.trim(), description: description.trim(), allowedOrigins: origins });
|
|
31
|
-
} catch (err) {
|
|
32
|
-
errorMsg = err instanceof Error ? err.message : 'Failed to save';
|
|
33
|
-
} finally {
|
|
34
|
-
saving = false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
<form onsubmit={handleSubmit} class="space-y-4">
|
|
40
|
-
<h3 class="text-lg font-semibold text-foreground">
|
|
41
|
-
{project ? 'Edit Project' : 'New Project'}
|
|
42
|
-
</h3>
|
|
43
|
-
|
|
44
|
-
<div>
|
|
45
|
-
<label for="sf-proj-name" class="mb-1 block text-sm font-semibold text-foreground">
|
|
46
|
-
Name <span class="text-red-500">*</span>
|
|
47
|
-
</label>
|
|
48
|
-
<input
|
|
49
|
-
id="sf-proj-name"
|
|
50
|
-
type="text"
|
|
51
|
-
bind:value={name}
|
|
52
|
-
required
|
|
53
|
-
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<div>
|
|
58
|
-
<label for="sf-proj-desc" class="mb-1 block text-sm font-semibold text-foreground">
|
|
59
|
-
Description
|
|
60
|
-
</label>
|
|
61
|
-
<textarea
|
|
62
|
-
id="sf-proj-desc"
|
|
63
|
-
bind:value={description}
|
|
64
|
-
rows="2"
|
|
65
|
-
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-foreground"
|
|
66
|
-
></textarea>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div>
|
|
70
|
-
<label for="sf-proj-origins" class="mb-1 block text-sm font-semibold text-foreground">
|
|
71
|
-
Allowed Origins <span class="text-foreground-tertiary text-xs">(one per line)</span>
|
|
72
|
-
</label>
|
|
73
|
-
<textarea
|
|
74
|
-
id="sf-proj-origins"
|
|
75
|
-
bind:value={originsText}
|
|
76
|
-
rows="3"
|
|
77
|
-
placeholder="https://example.com"
|
|
78
|
-
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-xs font-mono text-foreground"
|
|
79
|
-
></textarea>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
{#if errorMsg}
|
|
83
|
-
<div class="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-500">
|
|
84
|
-
{errorMsg}
|
|
85
|
-
</div>
|
|
86
|
-
{/if}
|
|
87
|
-
|
|
88
|
-
<div class="flex gap-2">
|
|
89
|
-
<button
|
|
90
|
-
type="submit"
|
|
91
|
-
disabled={saving || !name.trim()}
|
|
92
|
-
class="rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:opacity-50"
|
|
93
|
-
>
|
|
94
|
-
{saving ? 'Saving...' : project ? 'Update' : 'Create'}
|
|
95
|
-
</button>
|
|
96
|
-
{#if onCancel}
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
onclick={onCancel}
|
|
100
|
-
class="rounded-md border border-border px-4 py-2 text-sm text-foreground-secondary transition-colors hover:bg-surface-hover"
|
|
101
|
-
>
|
|
102
|
-
Cancel
|
|
103
|
-
</button>
|
|
104
|
-
{/if}
|
|
105
|
-
</div>
|
|
106
|
-
</form>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Project } from '../../types.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
project?: Project;
|
|
4
|
-
onSave?: (data: {
|
|
5
|
-
name: string;
|
|
6
|
-
description: string;
|
|
7
|
-
allowedOrigins: string[];
|
|
8
|
-
}) => Promise<void>;
|
|
9
|
-
onCancel?: () => void;
|
|
10
|
-
}
|
|
11
|
-
declare const ProjectForm: import("svelte").Component<Props, {}, "">;
|
|
12
|
-
type ProjectForm = ReturnType<typeof ProjectForm>;
|
|
13
|
-
export default ProjectForm;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Project } from '../../types.js';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
projects: Project[];
|
|
6
|
-
selectedId?: string;
|
|
7
|
-
onSelect?: (project: Project) => void;
|
|
8
|
-
onCreate?: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let { projects, selectedId, onSelect, onCreate }: Props = $props();
|
|
12
|
-
|
|
13
|
-
function formatDate(timestamp: number): string {
|
|
14
|
-
return new Date(timestamp).toLocaleDateString();
|
|
15
|
-
}
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<div class="space-y-3">
|
|
19
|
-
<div class="flex items-center justify-between">
|
|
20
|
-
<h2 class="text-lg font-semibold text-foreground">Projects</h2>
|
|
21
|
-
{#if onCreate}
|
|
22
|
-
<button
|
|
23
|
-
onclick={onCreate}
|
|
24
|
-
class="rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary-hover"
|
|
25
|
-
>
|
|
26
|
-
<i class="fa-solid fa-plus mr-1"></i>
|
|
27
|
-
New Project
|
|
28
|
-
</button>
|
|
29
|
-
{/if}
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
{#if projects.length === 0}
|
|
33
|
-
<div class="rounded-lg border border-border bg-surface p-8 text-center">
|
|
34
|
-
<i class="fa-solid fa-folder-open mb-2 text-2xl text-foreground-tertiary"></i>
|
|
35
|
-
<p class="text-sm text-foreground-secondary">No projects yet</p>
|
|
36
|
-
</div>
|
|
37
|
-
{:else}
|
|
38
|
-
<div class="space-y-2">
|
|
39
|
-
{#each projects as project}
|
|
40
|
-
<button
|
|
41
|
-
onclick={() => onSelect?.(project)}
|
|
42
|
-
class="w-full rounded-lg border p-3 text-left transition-colors {selectedId === project.id
|
|
43
|
-
? 'border-primary bg-primary/5'
|
|
44
|
-
: 'border-border bg-surface hover:bg-surface-hover'}"
|
|
45
|
-
>
|
|
46
|
-
<div class="flex items-center justify-between">
|
|
47
|
-
<span class="text-sm font-semibold text-foreground">{project.name}</span>
|
|
48
|
-
<span class="text-xs text-foreground-tertiary">{formatDate(project.createdAt)}</span>
|
|
49
|
-
</div>
|
|
50
|
-
{#if project.description}
|
|
51
|
-
<p class="mt-1 text-xs text-foreground-tertiary">{project.description}</p>
|
|
52
|
-
{/if}
|
|
53
|
-
</button>
|
|
54
|
-
{/each}
|
|
55
|
-
</div>
|
|
56
|
-
{/if}
|
|
57
|
-
</div>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Project } from '../../types.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
projects: Project[];
|
|
4
|
-
selectedId?: string;
|
|
5
|
-
onSelect?: (project: Project) => void;
|
|
6
|
-
onCreate?: () => void;
|
|
7
|
-
}
|
|
8
|
-
declare const ProjectList: import("svelte").Component<Props, {}, "">;
|
|
9
|
-
type ProjectList = ReturnType<typeof ProjectList>;
|
|
10
|
-
export default ProjectList;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { System, SystemStatus } from '../../types.js';
|
|
3
|
-
import StatusIndicator from '../shared/StatusIndicator.svelte';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
systems: System[];
|
|
7
|
-
onUpdateStatus?: (systemId: string, status: SystemStatus) => Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { systems, onUpdateStatus }: Props = $props();
|
|
11
|
-
|
|
12
|
-
let updating = $state<string | null>(null);
|
|
13
|
-
|
|
14
|
-
const statuses: { value: SystemStatus; label: string; icon: string }[] = [
|
|
15
|
-
{ value: 'operational', label: 'Operational', icon: 'fa-circle-check' },
|
|
16
|
-
{ value: 'degraded', label: 'Degraded', icon: 'fa-exclamation-triangle' },
|
|
17
|
-
{ value: 'outage', label: 'Outage', icon: 'fa-circle-xmark' },
|
|
18
|
-
{ value: 'maintenance', label: 'Maintenance', icon: 'fa-wrench' },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
async function setStatus(systemId: string, status: SystemStatus) {
|
|
22
|
-
updating = systemId;
|
|
23
|
-
try {
|
|
24
|
-
await onUpdateStatus?.(systemId, status);
|
|
25
|
-
} finally {
|
|
26
|
-
updating = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<div class="space-y-4">
|
|
32
|
-
<h3 class="text-base font-semibold text-foreground">
|
|
33
|
-
<i class="fa-solid fa-toggle-on mr-1.5 text-foreground-tertiary"></i>
|
|
34
|
-
Quick Status Update
|
|
35
|
-
</h3>
|
|
36
|
-
|
|
37
|
-
{#each systems as system}
|
|
38
|
-
<div class="rounded-lg border border-border bg-surface p-3">
|
|
39
|
-
<div class="flex items-center gap-2 mb-2">
|
|
40
|
-
<StatusIndicator status={system.status} size="sm" />
|
|
41
|
-
<span class="text-sm font-semibold text-foreground">{system.name}</span>
|
|
42
|
-
{#if updating === system.id}
|
|
43
|
-
<i class="fa-solid fa-spinner fa-spin text-xs text-foreground-tertiary"></i>
|
|
44
|
-
{/if}
|
|
45
|
-
</div>
|
|
46
|
-
<div class="grid grid-cols-2 gap-1 sm:grid-cols-4">
|
|
47
|
-
{#each statuses as s}
|
|
48
|
-
<button
|
|
49
|
-
onclick={() => setStatus(system.id, s.value)}
|
|
50
|
-
disabled={updating === system.id}
|
|
51
|
-
class="rounded px-2 py-1.5 text-xs transition-colors {system.status === s.value
|
|
52
|
-
? 'bg-primary text-primary-foreground'
|
|
53
|
-
: 'border border-border text-foreground-secondary hover:bg-surface-hover'}"
|
|
54
|
-
>
|
|
55
|
-
<i class="fa-solid {s.icon} mr-1"></i>
|
|
56
|
-
{s.label}
|
|
57
|
-
</button>
|
|
58
|
-
{/each}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
{/each}
|
|
62
|
-
</div>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { System, SystemStatus } from '../../types.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
systems: System[];
|
|
4
|
-
onUpdateStatus?: (systemId: string, status: SystemStatus) => Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
declare const StatusControl: import("svelte").Component<Props, {}, "">;
|
|
7
|
-
type StatusControl = ReturnType<typeof StatusControl>;
|
|
8
|
-
export default StatusControl;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { System, SystemStatus } from '../../types.js';
|
|
3
|
-
import StatusIndicator from '../shared/StatusIndicator.svelte';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
systems: System[];
|
|
7
|
-
onAdd?: (data: { name: string; description: string }) => Promise<void>;
|
|
8
|
-
onUpdateStatus?: (systemId: string, status: SystemStatus) => Promise<void>;
|
|
9
|
-
onDelete?: (systemId: string) => Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { systems, onAdd, onUpdateStatus, onDelete }: Props = $props();
|
|
13
|
-
|
|
14
|
-
let showAddForm = $state(false);
|
|
15
|
-
let newName = $state('');
|
|
16
|
-
let newDescription = $state('');
|
|
17
|
-
let adding = $state(false);
|
|
18
|
-
|
|
19
|
-
const statuses: SystemStatus[] = ['operational', 'degraded', 'outage', 'maintenance'];
|
|
20
|
-
|
|
21
|
-
async function handleAdd(e: Event) {
|
|
22
|
-
e.preventDefault();
|
|
23
|
-
if (!newName.trim()) return;
|
|
24
|
-
adding = true;
|
|
25
|
-
try {
|
|
26
|
-
await onAdd?.({ name: newName.trim(), description: newDescription.trim() });
|
|
27
|
-
newName = '';
|
|
28
|
-
newDescription = '';
|
|
29
|
-
showAddForm = false;
|
|
30
|
-
} finally {
|
|
31
|
-
adding = false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function handleDelete(systemId: string, systemName: string) {
|
|
36
|
-
if (!confirm(`Delete "${systemName}"? This cannot be undone.`)) return;
|
|
37
|
-
await onDelete?.(systemId);
|
|
38
|
-
}
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<div class="space-y-4">
|
|
42
|
-
<div class="flex items-center justify-between">
|
|
43
|
-
<h3 class="text-base font-semibold text-foreground">
|
|
44
|
-
<i class="fa-solid fa-server mr-1.5 text-foreground-tertiary"></i>
|
|
45
|
-
Systems
|
|
46
|
-
</h3>
|
|
47
|
-
<button
|
|
48
|
-
onclick={() => (showAddForm = !showAddForm)}
|
|
49
|
-
class="rounded-md border border-border px-2.5 py-1.5 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
|
|
50
|
-
>
|
|
51
|
-
<i class="fa-solid {showAddForm ? 'fa-minus' : 'fa-plus'} mr-1"></i>
|
|
52
|
-
{showAddForm ? 'Cancel' : 'Add System'}
|
|
53
|
-
</button>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
{#if showAddForm}
|
|
57
|
-
<form onsubmit={handleAdd} class="rounded-lg border border-border bg-surface p-3 space-y-3">
|
|
58
|
-
<input
|
|
59
|
-
type="text"
|
|
60
|
-
bind:value={newName}
|
|
61
|
-
placeholder="System name"
|
|
62
|
-
required
|
|
63
|
-
class="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground"
|
|
64
|
-
/>
|
|
65
|
-
<input
|
|
66
|
-
type="text"
|
|
67
|
-
bind:value={newDescription}
|
|
68
|
-
placeholder="Description (optional)"
|
|
69
|
-
class="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground"
|
|
70
|
-
/>
|
|
71
|
-
<button
|
|
72
|
-
type="submit"
|
|
73
|
-
disabled={adding || !newName.trim()}
|
|
74
|
-
class="rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground disabled:opacity-50"
|
|
75
|
-
>
|
|
76
|
-
{adding ? 'Adding...' : 'Add System'}
|
|
77
|
-
</button>
|
|
78
|
-
</form>
|
|
79
|
-
{/if}
|
|
80
|
-
|
|
81
|
-
{#if systems.length === 0}
|
|
82
|
-
<p class="text-sm text-foreground-tertiary">No systems defined yet.</p>
|
|
83
|
-
{:else}
|
|
84
|
-
<div class="space-y-2">
|
|
85
|
-
{#each systems as system}
|
|
86
|
-
<div class="rounded-lg border border-border bg-surface p-3">
|
|
87
|
-
<div class="flex items-center justify-between">
|
|
88
|
-
<div class="flex items-center gap-2">
|
|
89
|
-
<StatusIndicator status={system.status} size="sm" />
|
|
90
|
-
<span class="text-sm font-semibold text-foreground">{system.name}</span>
|
|
91
|
-
</div>
|
|
92
|
-
<button
|
|
93
|
-
onclick={() => handleDelete(system.id, system.name)}
|
|
94
|
-
class="text-xs text-foreground-tertiary transition-colors hover:text-red-500"
|
|
95
|
-
aria-label="Delete system"
|
|
96
|
-
>
|
|
97
|
-
<i class="fa-solid fa-trash"></i>
|
|
98
|
-
</button>
|
|
99
|
-
</div>
|
|
100
|
-
{#if system.description}
|
|
101
|
-
<p class="mt-1 text-xs text-foreground-tertiary">{system.description}</p>
|
|
102
|
-
{/if}
|
|
103
|
-
<div class="mt-2 flex gap-1">
|
|
104
|
-
{#each statuses as s}
|
|
105
|
-
<button
|
|
106
|
-
onclick={() => onUpdateStatus?.(system.id, s)}
|
|
107
|
-
class="rounded px-2 py-1 text-xs transition-colors {system.status === s
|
|
108
|
-
? 'bg-primary text-primary-foreground'
|
|
109
|
-
: 'border border-border text-foreground-secondary hover:bg-surface-hover'}"
|
|
110
|
-
>
|
|
111
|
-
{s}
|
|
112
|
-
</button>
|
|
113
|
-
{/each}
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
{/each}
|
|
117
|
-
</div>
|
|
118
|
-
{/if}
|
|
119
|
-
</div>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { System, SystemStatus } from '../../types.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
systems: System[];
|
|
4
|
-
onAdd?: (data: {
|
|
5
|
-
name: string;
|
|
6
|
-
description: string;
|
|
7
|
-
}) => Promise<void>;
|
|
8
|
-
onUpdateStatus?: (systemId: string, status: SystemStatus) => Promise<void>;
|
|
9
|
-
onDelete?: (systemId: string) => Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
declare const SystemManager: import("svelte").Component<Props, {}, "">;
|
|
12
|
-
type SystemManager = ReturnType<typeof SystemManager>;
|
|
13
|
-
export default SystemManager;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { default as AdminDashboard } from './AdminDashboard.svelte';
|
|
2
|
-
export { default as ProjectList } from './ProjectList.svelte';
|
|
3
|
-
export { default as ProjectForm } from './ProjectForm.svelte';
|
|
4
|
-
export { default as SystemManager } from './SystemManager.svelte';
|
|
5
|
-
export { default as StatusControl } from './StatusControl.svelte';
|
|
6
|
-
export { default as IncidentManager } from './IncidentManager.svelte';
|
|
7
|
-
export { default as IncidentForm } from './IncidentForm.svelte';
|
|
8
|
-
export { default as FeedbackList } from './FeedbackList.svelte';
|
|
9
|
-
export { default as ApiKeyManager } from './ApiKeyManager.svelte';
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { default as AdminDashboard } from './AdminDashboard.svelte';
|
|
2
|
-
export { default as ProjectList } from './ProjectList.svelte';
|
|
3
|
-
export { default as ProjectForm } from './ProjectForm.svelte';
|
|
4
|
-
export { default as SystemManager } from './SystemManager.svelte';
|
|
5
|
-
export { default as StatusControl } from './StatusControl.svelte';
|
|
6
|
-
export { default as IncidentManager } from './IncidentManager.svelte';
|
|
7
|
-
export { default as IncidentForm } from './IncidentForm.svelte';
|
|
8
|
-
export { default as FeedbackList } from './FeedbackList.svelte';
|
|
9
|
-
export { default as ApiKeyManager } from './ApiKeyManager.svelte';
|
package/dist/server/db.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database connection management
|
|
3
|
-
* Provides a singleton connection pool for MSSQL
|
|
4
|
-
*/
|
|
5
|
-
import type { ConnectionPool } from 'mssql';
|
|
6
|
-
/**
|
|
7
|
-
* Get the database connection pool. Creates one if it doesn't exist.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getPool(connectionString: string): Promise<ConnectionPool>;
|
|
10
|
-
/**
|
|
11
|
-
* Initialize the database: connect and run migrations
|
|
12
|
-
*/
|
|
13
|
-
export declare function initDatabase(connectionString: string): Promise<ConnectionPool>;
|
|
14
|
-
/**
|
|
15
|
-
* Close the database connection
|
|
16
|
-
*/
|
|
17
|
-
export declare function closeDatabase(): Promise<void>;
|
package/dist/server/db.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database connection management
|
|
3
|
-
* Provides a singleton connection pool for MSSQL
|
|
4
|
-
*/
|
|
5
|
-
import sql from 'mssql';
|
|
6
|
-
import { runMigrations } from './schema.js';
|
|
7
|
-
let _pool = null;
|
|
8
|
-
let _initialized = false;
|
|
9
|
-
/**
|
|
10
|
-
* Parse a connection string into mssql config
|
|
11
|
-
*/
|
|
12
|
-
function parseConnectionString(connectionString) {
|
|
13
|
-
const params = {};
|
|
14
|
-
for (const part of connectionString.split(';')) {
|
|
15
|
-
const [key, ...valueParts] = part.split('=');
|
|
16
|
-
if (key && valueParts.length > 0) {
|
|
17
|
-
params[key.trim().toLowerCase()] = valueParts.join('=').trim();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
server: params['server'] || params['data source'] || '',
|
|
22
|
-
database: params['initial catalog'] || params['database'] || '',
|
|
23
|
-
user: params['user id'] || params['uid'] || '',
|
|
24
|
-
password: params['password'] || params['pwd'] || '',
|
|
25
|
-
options: {
|
|
26
|
-
encrypt: params['encrypt']?.toLowerCase() !== 'false',
|
|
27
|
-
trustServerCertificate: params['trustservercertificate']?.toLowerCase() === 'true',
|
|
28
|
-
},
|
|
29
|
-
pool: {
|
|
30
|
-
max: 10,
|
|
31
|
-
min: 0,
|
|
32
|
-
idleTimeoutMillis: 30000,
|
|
33
|
-
},
|
|
34
|
-
requestTimeout: parseInt(params['connection timeout'] || '30') * 1000,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Get the database connection pool. Creates one if it doesn't exist.
|
|
39
|
-
*/
|
|
40
|
-
export async function getPool(connectionString) {
|
|
41
|
-
if (_pool?.connected) {
|
|
42
|
-
return _pool;
|
|
43
|
-
}
|
|
44
|
-
const config = parseConnectionString(connectionString);
|
|
45
|
-
_pool = await sql.connect(config);
|
|
46
|
-
return _pool;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Initialize the database: connect and run migrations
|
|
50
|
-
*/
|
|
51
|
-
export async function initDatabase(connectionString) {
|
|
52
|
-
const pool = await getPool(connectionString);
|
|
53
|
-
if (!_initialized) {
|
|
54
|
-
await runMigrations(pool);
|
|
55
|
-
_initialized = true;
|
|
56
|
-
}
|
|
57
|
-
return pool;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Close the database connection
|
|
61
|
-
*/
|
|
62
|
-
export async function closeDatabase() {
|
|
63
|
-
if (_pool) {
|
|
64
|
-
await _pool.close();
|
|
65
|
-
_pool = null;
|
|
66
|
-
_initialized = false;
|
|
67
|
-
}
|
|
68
|
-
}
|