From fef9d30cdab6a64bf2a0210300b129db57f935aa Mon Sep 17 00:00:00 2001 From: Maya Date: Thu, 14 Aug 2025 01:01:01 -0400 Subject: [PATCH] ui tweaks --- src/lib/components/cards/NodeCard.svelte | 151 ++++++ src/lib/components/cards/NodeGroupCard.svelte | 103 ++++ src/lib/components/common/Card.svelte | 118 ++++ src/lib/components/common/EditModal.svelte | 87 +++ src/lib/components/common/ListManager.svelte | 137 +++++ .../{shared => common}/Notifications.svelte | 0 .../{shared => common}/Sidebar.svelte | 0 src/lib/components/modals/NodeEditor.svelte | 512 +++++++++--------- .../components/modals/NodeGroupEditor.svelte | 364 +++++-------- .../components/modals/TestAssignment.svelte | 256 +++++++-- src/lib/components/nodes/NodeCard.svelte | 186 ------- .../components/nodes/NodeTypeSelector.svelte | 0 .../components/nodes/TestConfigForm.svelte | 0 src/lib/components/shared/Card.svelte | 0 src/lib/components/shared/Modal.svelte | 0 src/lib/components/tabs/NodesTab.svelte | 57 +- src/lib/types/index.ts | 34 ++ src/lib/types/tests.ts | 36 +- src/routes/+page.svelte | 2 +- 19 files changed, 1291 insertions(+), 752 deletions(-) create mode 100644 src/lib/components/cards/NodeCard.svelte create mode 100644 src/lib/components/cards/NodeGroupCard.svelte create mode 100644 src/lib/components/common/Card.svelte create mode 100644 src/lib/components/common/EditModal.svelte create mode 100644 src/lib/components/common/ListManager.svelte rename src/lib/components/{shared => common}/Notifications.svelte (100%) rename src/lib/components/{shared => common}/Sidebar.svelte (100%) delete mode 100644 src/lib/components/nodes/NodeCard.svelte delete mode 100644 src/lib/components/nodes/NodeTypeSelector.svelte delete mode 100644 src/lib/components/nodes/TestConfigForm.svelte delete mode 100644 src/lib/components/shared/Card.svelte delete mode 100644 src/lib/components/shared/Modal.svelte diff --git a/src/lib/components/cards/NodeCard.svelte b/src/lib/components/cards/NodeCard.svelte new file mode 100644 index 00000000..73ef0167 --- /dev/null +++ b/src/lib/components/cards/NodeCard.svelte @@ -0,0 +1,151 @@ + + + + \ No newline at end of file diff --git a/src/lib/components/cards/NodeGroupCard.svelte b/src/lib/components/cards/NodeGroupCard.svelte new file mode 100644 index 00000000..8195ebaa --- /dev/null +++ b/src/lib/components/cards/NodeGroupCard.svelte @@ -0,0 +1,103 @@ + + + + \ No newline at end of file diff --git a/src/lib/components/common/Card.svelte b/src/lib/components/common/Card.svelte new file mode 100644 index 00000000..790c549f --- /dev/null +++ b/src/lib/components/common/Card.svelte @@ -0,0 +1,118 @@ + + + +
+ +
+
+ {#if icon} + + {/if} +
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+
+ {#if status} + {status} + {/if} +
+ + +
+ + {#each sections as section} +
+ {section.label}: + {section.value} +
+ {/each} + + + {#each lists as list} +
+ {list.label}: + {#if list.items.length > 0} +
+ {#each list.items as item} +
+
+ {#if list.renderItem} + {@html list.renderItem(item)} + {:else} + + {item.label} + {#if item.disabled} + (disabled) + {/if} + + {/if} +
+ {#if list.itemActions} +
+ {#each list.itemActions(item) as action} + + {/each} +
+ {/if} +
+ {/each} +
+ {:else} + {list.emptyText || `No ${list.label.toLowerCase()}`} + {/if} +
+ {/each} +
+ + + {#if actions.length > 0} +
+ {#each actions as action} + + {/each} +
+ {/if} +
+ + \ No newline at end of file diff --git a/src/lib/components/common/EditModal.svelte b/src/lib/components/common/EditModal.svelte new file mode 100644 index 00000000..255e5f9d --- /dev/null +++ b/src/lib/components/common/EditModal.svelte @@ -0,0 +1,87 @@ + + + +{#if isOpen} +
+
+
+

{title}

+ +
+ +
+ + + + +
+ +
+ {#if onDelete} + + {/if} +
+ + +
+ + +
+
+ +
+
+{/if} \ No newline at end of file diff --git a/src/lib/components/common/ListManager.svelte b/src/lib/components/common/ListManager.svelte new file mode 100644 index 00000000..eddcdabb --- /dev/null +++ b/src/lib/components/common/ListManager.svelte @@ -0,0 +1,137 @@ + + +
+ + + +
+ + +
+ + + {#if items.length > 0} +
+ {#each items as itemId, index} +
+ {#if allowReorder} + {index + 1}. + {/if} + {getDisplayName(itemId)} + +
+ {#if allowReorder} + + + + {/if} + + +
+
+ {/each} +
+ {:else} +
+ No {label.toLowerCase()} added yet +
+ {/if} + + + {#if error} +

{error}

+ {/if} +
\ No newline at end of file diff --git a/src/lib/components/shared/Notifications.svelte b/src/lib/components/common/Notifications.svelte similarity index 100% rename from src/lib/components/shared/Notifications.svelte rename to src/lib/components/common/Notifications.svelte diff --git a/src/lib/components/shared/Sidebar.svelte b/src/lib/components/common/Sidebar.svelte similarity index 100% rename from src/lib/components/shared/Sidebar.svelte rename to src/lib/components/common/Sidebar.svelte diff --git a/src/lib/components/modals/NodeEditor.svelte b/src/lib/components/modals/NodeEditor.svelte index d6fbc7e7..414c2bd8 100644 --- a/src/lib/components/modals/NodeEditor.svelte +++ b/src/lib/components/modals/NodeEditor.svelte @@ -1,14 +1,18 @@ -{#if isOpen} -
-
-
-

- {node ? 'Edit Node' : 'Create Node'} -

- -
- -
- -
- -
- - -
- - -
- - -
-
- - -
- -
- - -
- - -
- - -
-
- -
- -
- - -
- - -
- - -
-
- - -
- - -
- - -
- -
- {#each nodeCapabilities as capability} - - {/each} -
-
- - -
- -
- - -
- - -
-
+ + +
+
+ + + {#if errors.name} +

{errors.name}

+ {/if} +
+ +
+ +
-{/if} - - \ No newline at end of file + + +
+
+ + +
+ +
+ + +
+ +
+ + + {#if errors.port} +

{errors.port}

+ {/if} +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+ + +
+ + + ({ + 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}

- -
- - -
- -
-
- - - {#if errors.name} -

{errors.name}

- {/if} -
- -
- - -
-
- - -
- - -
- - -
- - - -
- - -
- - - {#if formData.node_sequence.length > 0} -
- {#each formData.node_sequence as nodeId, index} -
- {index + 1}. - {getNodeName(nodeId)} - -
- - - - - -
-
- {/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. -

-
- - -
- - -
-
-
+ + +
+ + + {#if errors.name} +

{errors.name}

+ {/if}
-{/if} \ No newline at end of file + +
+ + +
+ + +
+ +

+ 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';