-
-
- {node ? 'Edit Node' : 'Create Node'}
-
-
-
-
-
-
-
+
+
+
+
+
+ Name *
+
+
+ {#if errors.name}
+
{errors.name}
+ {/if}
+
+
+
+
+ Node Type
+
+
+ Select node type
+ {#each nodeTypes as type}
+ {getNodeTypeDisplayName(type)}
+ {/each}
+
-{/if}
-
-
\ No newline at end of file
+
+
+
+
+
+ IP Address
+
+
+
+
+
+
+ Domain/Hostname
+
+
+
+
+
+
+ Port
+
+
+ {#if errors.port}
+
{errors.port}
+ {/if}
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+
+ ({
+ id: cap,
+ label: getCapabilityDisplayName(cap)
+ }))}
+ placeholder="Select a capability to add"
+ allowReorder={false}
+ getDisplayName={(id: string) => getCapabilityDisplayName(id as NodeCapability)}
+ />
+
+
+
+
+
+
+
+ Note: Test assignments are managed separately through the "Assign Test" button on the node card,
+ as they require detailed configuration beyond just the test type.
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/modals/NodeGroupEditor.svelte b/src/lib/components/modals/NodeGroupEditor.svelte
index deab3953..edd2e0dc 100644
--- a/src/lib/components/modals/NodeGroupEditor.svelte
+++ b/src/lib/components/modals/NodeGroupEditor.svelte
@@ -1,13 +1,16 @@
-{#if isOpen}
-
e.key === 'Escape' && handleClose()}
- role="dialog"
- aria-modal="true"
- tabindex="-1"
- >
-
-
-
-
{title}
-
-
-
-
-
-
-
-
-
-
-
- Group Name *
-
-
- {#if errors.name}
-
{errors.name}
- {/if}
-
-
-
-
-
- Enable auto-diagnostic
-
-
-
-
-
-
-
- Description
-
-
-
-
-
-
-
- Diagnostic Sequence *
-
-
-
-
-
- Select a node to add
- {#each availableNodes as node}
- {node.name} ({node.ip || node.domain || 'No address'})
- {/each}
-
-
- Add
-
-
-
-
- {#if formData.node_sequence.length > 0}
-
- {#each formData.node_sequence as nodeId, index}
-
-
{index + 1}.
-
{getNodeName(nodeId)}
-
-
-
moveNodeUp(index)}
- disabled={index === 0}
- class="p-1 text-gray-400 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed"
- title="Move up"
- >
-
-
-
-
moveNodeDown(index)}
- disabled={index === formData.node_sequence.length - 1}
- class="p-1 text-gray-400 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed"
- title="Move down"
- >
-
-
-
-
removeNode(nodeId)}
- class="p-1 text-red-400 hover:text-red-300"
- title="Remove node"
- >
-
-
-
-
- {/each}
-
- {:else}
-
-
No nodes in sequence
-
Add nodes above to define the diagnostic order
-
- {/if}
-
- {#if errors.nodes}
-
{errors.nodes}
- {/if}
-
-
- Diagnostics will run tests on nodes in this order. Drag to reorder or use the arrow buttons.
-
-
-
-
-
-
- Cancel
-
-
- {#if loading}
- Saving...
- {:else}
- {isEditing ? 'Update' : 'Create'} Group
- {/if}
-
-
-
-
+
+
+
+
+ Group Name *
+
+
+ {#if errors.name}
+
{errors.name}
+ {/if}
-{/if}
\ No newline at end of file
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ Enable Auto-Diagnostic
+
+
+ When enabled, this diagnostic will run automatically when any node in the group fails a test
+
+
+
+
+ ({
+ id: node.id,
+ label: node.name,
+ subtitle: node.ip || node.domain || 'No address'
+ }))}
+ placeholder="Select a node to add"
+ required={true}
+ allowReorder={true}
+ getDisplayName={getNodeName}
+ error={errors.nodes}
+ />
+
+
+
+
+ Diagnostic Sequence: Tests will be executed on nodes in the order specified above.
+ This allows you to follow logical network paths and dependencies during troubleshooting.
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/modals/TestAssignment.svelte b/src/lib/components/modals/TestAssignment.svelte
index 837a1728..3d848149 100644
--- a/src/lib/components/modals/TestAssignment.svelte
+++ b/src/lib/components/modals/TestAssignment.svelte
@@ -1,12 +1,13 @@
-
-
-
-
-
-
-
-
{node.name}
-
- {node.node_type ? getNodeTypeDisplayName(node.node_type) : 'Unknown Device'}
-
-
-
-
- {getDisplayStatus()}
-
-
-
-
-
-
- {#if node.ip || node.domain}
-
- {#if node.ip}
- IP: {node.ip}
- {#if node.port}:{node.port}{/if}
- {:else if node.domain}
- Domain: {node.domain}
- {#if node.port}:{node.port}{/if}
- {/if}
-
- {/if}
-
-
-
-
- Capabilities:
- {#if node.capabilities && node.capabilities.length > 0}
-
- {#each node.capabilities as capability, i}
-
- {capability}
-
- {/each}
-
- {:else}
- No capabilities assigned
- {/if}
-
-
-
-
-
- Groups:
- {#if groupNames.length > 0}
-
- {#each groupNames as groupName, i}
-
- {groupName}
-
- {/each}
-
- {:else}
- No groups assigned
- {/if}
-
-
-
-
-
Tests:
-
- {#if node.assigned_tests && node.assigned_tests.length > 0}
-
- {#each node.assigned_tests as test}
-
-
{test.test_type}
-
-
- {test.criticality}
-
- {#if test.monitor_interval_minutes}
-
- {test.monitor_interval_minutes}m
-
- {/if}
- {#if !test.enabled}
- (disabled)
- {/if}
-
-
- {/each}
-
- {:else}
- No tests assigned
- {/if}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/lib/components/nodes/NodeTypeSelector.svelte b/src/lib/components/nodes/NodeTypeSelector.svelte
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/components/nodes/TestConfigForm.svelte b/src/lib/components/nodes/TestConfigForm.svelte
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/components/shared/Card.svelte b/src/lib/components/shared/Card.svelte
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/components/shared/Modal.svelte b/src/lib/components/shared/Modal.svelte
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/components/tabs/NodesTab.svelte b/src/lib/components/tabs/NodesTab.svelte
index a0fd8ce6..f9b3bc28 100644
--- a/src/lib/components/tabs/NodesTab.svelte
+++ b/src/lib/components/tabs/NodesTab.svelte
@@ -3,16 +3,18 @@
import { Plus, Search } from 'lucide-svelte';
import { nodes, nodeActions, loading, error } from '../../stores/nodes';
import { nodeGroups, nodeGroupActions } from '../../stores/node-groups';
- import type { Node } from '../../types/nodes';
- import NodeCard from '../nodes/NodeCard.svelte';
+ import type { Node, AssignedTest } from '../../types/nodes';
+ import { getTestTypeDisplayName } from '../../types/tests';
+ import NodeCard from '../cards/NodeCard.svelte';
import NodeEditor from '../modals/NodeEditor.svelte';
import TestAssignment from '../modals/TestAssignment.svelte';
let searchTerm = '';
let showNodeEditor = false;
- let showTestAssignment = false;
+ let showTestEditor = false;
let editingNode: Node | null = null;
let assigningTestNode: Node | null = null;
+ let editingTest: AssignedTest | null = null;
$: filteredNodes = $nodes.filter((node: Node) =>
node.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -55,7 +57,44 @@
function handleAssignTest(node: Node) {
assigningTestNode = node;
- showTestAssignment = true;
+ showTestEditor = true;
+ }
+
+ async function handleDeleteTest(node: Node, test: AssignedTest) {
+ if (!confirm(`Are you sure you want to remove the ${getTestTypeDisplayName(test.test_type)} test from ${node.name}?`)) {
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/tests/unassign-test', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ node_id: node.id,
+ test_type: test.test_type
+ }),
+ });
+
+ if (response.ok) {
+ // Refresh nodes to get the updated node list
+ await nodeActions.loadNodes();
+ } else {
+ const error = await response.json();
+ alert(`Failed to remove test: ${error.error || 'Unknown error'}`);
+ }
+ } catch (error) {
+ console.error('Error removing test:', error);
+ alert('Failed to remove test. Please try again.');
+ }
+ }
+
+ // Also update your handleEditTest function to clear editingTest when closing
+ function handleEditTest(node: Node, test: AssignedTest) {
+ assigningTestNode = node;
+ editingTest = test;
+ showTestEditor = true;
}
// Updated to handle function props instead of events
@@ -86,7 +125,7 @@
// Refresh nodes to get the updated node with the new test
await nodeActions.loadNodes();
- showTestAssignment = false;
+ showTestEditor = false;
assigningTestNode = null;
}
@@ -96,8 +135,9 @@
}
function handleCloseTestAssignment() {
- showTestAssignment = false;
+ showTestEditor = false;
assigningTestNode = null;
+ editingTest = null;
}
@@ -197,6 +237,8 @@
onEdit={handleEditNode}
onDelete={handleDeleteNode}
onAssignTest={handleAssignTest}
+ onEditTest={handleEditTest}
+ onDeleteTest={handleDeleteTest}
/>
{/each}
@@ -213,8 +255,9 @@
/>
\ No newline at end of file
diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts
index e08ece33..986c0515 100644
--- a/src/lib/types/index.ts
+++ b/src/lib/types/index.ts
@@ -1,6 +1,40 @@
import type { Node } from "./nodes";
import type { TestType, TestResult } from "./tests";
+// Components
+
+export interface CardAction {
+ label: string;
+ icon: any;
+ color?: string;
+ hoverColor?: string;
+ bgHover?: string;
+ onClick: () => void;
+ disabled?: boolean;
+}
+
+export interface CardSection {
+ label: string;
+ value: string;
+}
+
+export interface CardList {
+ label: string;
+ items: CardListItem[];
+ emptyText?: string;
+ renderItem?: (item: CardListItem) => string;
+ itemActions?: (item: CardListItem) => CardAction[];
+}
+
+export interface CardListItem {
+ id: string;
+ label: string;
+ metadata?: Record
;
+ color?: string;
+ bgColor?: string;
+ disabled?: boolean;
+}
+
// API Response types
export interface ApiResponse {
success: boolean;
diff --git a/src/lib/types/tests.ts b/src/lib/types/tests.ts
index 2872deae..5fc654be 100644
--- a/src/lib/types/tests.ts
+++ b/src/lib/types/tests.ts
@@ -10,60 +10,64 @@ export type TestType = 'Connectivity' |
'DaemonCommand' |
'SshScript';
-export interface BaseTestConfig {
+export interface ConnectivityConfig {
timeout?: number;
expected_result: string;
-}
-
-export interface ConnectivityConfig {
- base: BaseTestConfig;
target: string;
port?: number;
protocol?: string;
}
export interface DirectIpConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
target: string;
port: number;
}
export interface PingConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
target: string;
port?: number;
attempts?: number;
}
export interface WellknownIpConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
}
export interface DnsResolutionConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
domain: string;
}
export interface DnsOverHttpsConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
target: string;
domain: string;
service_type?: string;
}
export interface VpnConnectivityConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
target: string;
port?: number;
}
export interface VpnTunnelConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
expected_subnet: string;
}
export interface ServiceHealthConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
target: string;
port?: number;
path?: string;
@@ -71,7 +75,8 @@ export interface ServiceHealthConfig {
}
export interface DaemonCommandConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
command: string;
requires_confirmation?: boolean;
rollback_command?: string;
@@ -79,7 +84,8 @@ export interface DaemonCommandConfig {
}
export interface SshScriptConfig {
- base: BaseTestConfig;
+ timeout?: number;
+ expected_result: string;
command: string;
ssh_target: string;
requires_confirmation?: boolean;
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 0aa55db6..e776c0e9 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import NodesTab from '../lib/components/tabs/NodesTab.svelte';
import DiagnosticsTab from '../lib/components/tabs/DiagnosticsTab.svelte';
- import Sidebar from '../lib/components/shared/Sidebar.svelte';
+ import Sidebar from '../lib/components/common/Sidebar.svelte';
import { nodeActions } from '../lib/stores/nodes';
let activeTab = 'nodes';