Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions backend/src/server/services/definitions/example.rs

This file was deleted.

64 changes: 44 additions & 20 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,7 @@ Discovery is the process of scanning your network to find hosts and services.

**Scheduled (Automatic)**
- Runs on a cron schedule
- Default: Hourly (`0 0 */1 * * *`)
- Can be customized to any cron expression
- Default: Every 24 hours (`0 0 */1 * * *`)
- Enable/disable without deleting

### Creating a Discovery
Expand Down Expand Up @@ -685,30 +684,64 @@ The topology view generates an interactive diagram of your network structure.
<img src="../media/topology_full.png" width="800" alt="Discovery Sessions">
</p>

### Topology State

Topology will display different states depending on if the underlying data has changed since it was last built. Pressing Rebuild will have different effects depending on topology state.

Rebuilding the graph will reset some user-provided changes, such as node positions and subnet sizes. Edge handles will be preserved when possible, but may also be overwritten as the layout of the graph may need to change depending on entities being added or removed.

**Up to date**
<p align="center">
<img src="../media/topology_fresh.png" width="800" alt="Discovery Sessions">
</p>
- All data is up to date
- There is usually no reason to rebuild the graph in this state

**Stale**
<p align="center">
<img src="../media/topology_stale.png" width="800" alt="Discovery Sessions">
</p>
- One or more entities have been added or updated since the visualization was last rebuilt
- Rebuilding the graph will add those entities

**Conflict**
<p align="center">
<img src="../media/topology_conflict.png" width="800" alt="Discovery Sessions">
</p>
- One or more entities on the graph have been deleted since it was last rebuilt
- Rebuilding the graph will remove those entities
- The specific entities that will be removed will be displayed in a modal so you can confirm before they are removed.

**Locked**
<p align="center">
<img src="../media/topology_locked.png" width="800" alt="Discovery Sessions">
</p>
- Other states will not be displayed
- Topology cannot be rebuilt
- Press Lock to enter the locked state

### Visual Elements

**Subnet Containers**
- Large rectangles grouping hosts by network segment
- Shows subnet name and CIDR
- Can be resized manually

**Host Nodes**
**Interface Nodes**
- Represent network interfaces
- Show services bound to that interface
- Display IP addresses and hostnames

**Service Nodes**
**Services**
- Icons representing detected services
- Show service name and ports
- Color-coded by category

**Edges**
- Lines connecting related nodes
- Different types:
- Host interfaces
- Group relationships
- Docker container links
- Gateway connections

**Left Zone**
- Optional section within each subnet
Expand All @@ -723,24 +756,12 @@ Access the options panel via the button on the right side of the topology view:
<img src="../media/topology_options_overview.png" width="800" alt="Topology Options">
</p>

**General Options**

*Network Selection*
- Choose which networks to include in the diagram
- Multi-select to overlay multiple networks
- Useful for comparing environments

*Service Category Filters*
- Hide specific categories (Media, Development, etc.)
- Reduces clutter for large networks
- Categories remain in data, just hidden from view

**Visual Options**

*Don't Fade Edges*
- Show all edges at full opacity
- Default behavior fades unselected edges
- Enable for clearer edge visibility
- Enable for clearer edge visibility and screenshot appearance

*Hide Resize Handles*
- Remove subnet resize handles from corners
Expand Down Expand Up @@ -801,7 +822,10 @@ Access the options panel via the button on the right side of the topology view:

**Node Positioning**
- Click and drag any node to reposition
- Reset by refreshing the topology

**Edge Handle**
- Select an edge to show handles (same-colored circle at edge ends)
- Move handles to other locations on node

**Subnet Sizing**
- Drag subnet corners to resize
Expand Down
Binary file added media/topology_conflict.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/topology_fresh.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified media/topology_full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/topology_locked.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/topology_stale.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 28 additions & 13 deletions ui/src/lib/features/topology/components/TopologyModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,42 @@
import {
createEmptyTopologyFormData,
createTopology,
topologies,
topology,
topologyOptions,
updateTopology
} from '../store';
import { entities } from '$lib/shared/stores/metadata';
import ModalHeaderIcon from '$lib/shared/components/layout/ModalHeaderIcon.svelte';
import TopologyDetailsForm from './TopologyDetailsForm.svelte';

export let isOpen = false;
export let onSubmit: () => Promise<void> | void;
export let onClose: () => void;
export let topo: Topology | null = null;
let {
isOpen = $bindable(false),
onSubmit,
onClose,
topo = null
}: {
isOpen: boolean;
onSubmit: () => Promise<void> | void;
onClose: () => void;
topo: Topology | null;
} = $props();

$: isEditing = topo != null;
$: title = isEditing ? `Edit ${topo?.name}` : 'Create Topology';
let isEditing = $derived(topo != null);
let title = $derived(isEditing ? `Edit ${topo?.name}` : 'Create Topology');

let loading = false;
let formData: Topology = createEmptyTopologyFormData();
let loading = $state(false);
let formData: Topology = $derived(topo ? { ...topo } : createEmptyTopologyFormData());

$effect(() => {
void $topology;
void $topologies;
});

// Reset form when modal opens
$: if (isOpen) {
resetForm();
}
$effect(() => {
if (isOpen) resetForm();
});

function resetForm() {
formData = topo ? { ...topo } : createEmptyTopologyFormData();
Expand All @@ -52,7 +66,8 @@
}
}

let colorHelper = entities.getColorHelper('Subnet');
let colorHelper = $state(entities.getColorHelper('Topology'));
let Icon = $state(entities.getIconComponent('Topology'));
</script>

<EditModal
Expand All @@ -67,7 +82,7 @@
let:formApi
>
<svelte:fragment slot="header-icon">
<ModalHeaderIcon Icon={entities.getIconComponent('Topology')} color={colorHelper.string} />
<ModalHeaderIcon {Icon} color={colorHelper.string} />
</svelte:fragment>

<div class="space-y-6">
Expand Down
9 changes: 7 additions & 2 deletions ui/src/lib/features/topology/components/TopologyTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@
import InlineWarning from '$lib/shared/components/feedback/InlineWarning.svelte';
import { formatTimestamp } from '$lib/shared/utils/formatting';

const loading = loadData([getHosts, getServices, getSubnets, getGroups, getTopologies]);

let isCreateEditOpen = $state(false);
let editingTopology: Topology | null = $state(null);

let isRefreshConflictsOpen = $state(false);

$effect(() => {
void $topology;
void $topologies;
});

function handleCreateTopology() {
isCreateEditOpen = true;
editingTopology = null;
Expand Down Expand Up @@ -134,8 +141,6 @@
let lockedByUser = $derived(
$topology?.locked_by ? $users.find((u) => u.id === $topology.locked_by) : null
);

const loading = loadData([getHosts, getServices, getSubnets, getGroups, getTopologies]);
</script>

<SvelteFlowProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
const edgeData = data as TopologyEdge;
const edgeTypeMetadata = edgeTypes.getMetadata(edgeData.edge_type);
$effect(() => {
void $topology;
});
// Get group reactively - updates when groups store changes
let group = $derived.by(() => {
if (!$topology?.groups) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
height = height ? height : 0;
width = width ? width : 0;
$effect(() => {
void $topology;
});
let host = $derived(
$topology ? $topology.hosts.find((h) => h.id == nodeData.host_id) : undefined
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
let nodeStyle = $derived(`width: ${width}px; height: ${height}px;`);
let hasInfra = $derived(infra_width > 0);
$effect(() => {
void $topology;
});
let subnet = $derived($topology ? $topology.subnets.find((s) => s.id == id) : undefined);
const viewport = useViewport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
optionsPanelExpanded,
selectedEdge,
selectedNode,
topologies,
topology,
updateTopology
} from '../../store';
Expand Down Expand Up @@ -48,6 +49,11 @@
// Store pending edges until nodes are ready
let pendingEdges: Edge[] = [];
$effect(() => {
void $topology;
void $topologies;
});
$effect(() => {
if ($topology && ($topology.edges || $topology.nodes)) {
void loadTopologyData();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
import { entities } from '$lib/shared/stores/metadata';

export const TopologyDisplay: EntityDisplayComponent<Topology, object> = {
Expand Down Expand Up @@ -35,8 +35,17 @@
import type { Topology } from '$lib/features/topology/types/base';
import { getTopologyStateInfo } from '$lib/features/topology/state';

export let item: Topology;
export let context = {};
let {
item,
context = {}
}: {
item: Topology;
context: object;
} = $props();

$effect(() => {
void entities;
});
</script>

<ListSelectItem {item} {context} displayComponent={TopologyDisplay} />