mirror of
https://github.com/mayanayza/netvisor.git
synced 2025-12-10 08:24:08 -06:00
Merge pull request #57 from mayanayza/fix/binding-edits
fix: port bindings are now reactive to port changes
This commit is contained in:
@@ -98,7 +98,7 @@
|
||||
<div>
|
||||
<!-- Source host info -->
|
||||
<div class="mb-6 rounded-lg border border-gray-700 bg-gray-800/50 p-4">
|
||||
<EntityDisplay item={otherHost} displayComponent={HostDisplay} />
|
||||
<EntityDisplay context={{}} item={otherHost} displayComponent={HostDisplay} />
|
||||
</div>
|
||||
|
||||
<!-- Target selection -->
|
||||
|
||||
@@ -88,12 +88,17 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="config" let:selectedItem let:onChange>
|
||||
{#if selectedItem}
|
||||
{#if selectedItem && selectedItem.type == 'Custom'}
|
||||
<PortConfigPanel
|
||||
{formApi}
|
||||
port={selectedItem}
|
||||
onChange={(updatedPort) => onChange(updatedPort)}
|
||||
/>
|
||||
{:else if selectedItem && selectedItem.type != 'Custom'}
|
||||
<EntityConfigEmpty
|
||||
title="Standard Port"
|
||||
subtitle="This is a standard port, and can't be edited"
|
||||
/>
|
||||
{:else}
|
||||
<EntityConfigEmpty
|
||||
title="No port selected"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { form as createForm } from 'svelte-forms';
|
||||
import GenericModal from '../layout/GenericModal.svelte';
|
||||
import type { TextFieldType, FormApi, NumberFieldType } from './types';
|
||||
import { pushWarning } from '$lib/shared/stores/feedback';
|
||||
|
||||
export let title: string = 'Edit';
|
||||
export let isOpen: boolean = false;
|
||||
@@ -48,6 +49,7 @@
|
||||
|
||||
// Check if current fields are valid
|
||||
if (!$form.valid) {
|
||||
pushWarning('Form invalid: ' + $form.errors);
|
||||
return; // Don't proceed if validation fails
|
||||
} else {
|
||||
onSave?.();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" generics="T, V">
|
||||
<!-- T: Item type, V: Dropdown option type, C: type of context passed to item -->
|
||||
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
||||
<script lang="ts" generics="T, V, C">
|
||||
import { ArrowUp, ArrowDown, Trash2, Plus, Edit } from 'lucide-svelte';
|
||||
import RichSelect from './RichSelect.svelte';
|
||||
import ListSelectItem from './ListSelectItem.svelte';
|
||||
@@ -21,13 +23,13 @@
|
||||
|
||||
// Options (dropdown)
|
||||
export let options: V[] = [];
|
||||
export let optionDisplayComponent: EntityDisplayComponent<V>;
|
||||
export let optionDisplayComponent: EntityDisplayComponent<V, C>;
|
||||
export let showSearch: boolean = false;
|
||||
|
||||
// Items
|
||||
export let items: T[] = [];
|
||||
export let itemDisplayComponent: EntityDisplayComponent<T>;
|
||||
export let getItemContext: ((item: T, index: number) => Record<string, unknown>) | null = null;
|
||||
export let itemDisplayComponent: EntityDisplayComponent<T, C>;
|
||||
export let getItemContext: (item: T, index: number) => C = () => new Object() as C;
|
||||
export let formApi: FormApi;
|
||||
|
||||
// Item interaction
|
||||
@@ -166,7 +168,7 @@
|
||||
<!-- Use slot if provided, otherwise check for inline editing -->
|
||||
<slot name="item" {item} {index}>
|
||||
{#if editingIndex === index && itemDisplayComponent.supportsInlineEdit && itemDisplayComponent.renderInlineEdit}
|
||||
{@const context = getItemContext ? getItemContext(item, index) : undefined}
|
||||
{@const context = getItemContext(item, index)}
|
||||
{@const inlineEditConfig = itemDisplayComponent.renderInlineEdit(
|
||||
item,
|
||||
(updates) => {
|
||||
@@ -178,7 +180,7 @@
|
||||
)}
|
||||
<svelte:component this={inlineEditConfig.component} {...inlineEditConfig.props} />
|
||||
{:else}
|
||||
{@const context = getItemContext ? getItemContext(item, index) : undefined}
|
||||
{@const context = getItemContext(item, index)}
|
||||
<ListSelectItem {item} {context} displayComponent={itemDisplayComponent} />
|
||||
{/if}
|
||||
</slot>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts" generics="T">
|
||||
<!-- T: Item type, C: type of context passed to item -->
|
||||
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
||||
<script lang="ts" generics="T, C">
|
||||
import Tag from '../../data/Tag.svelte';
|
||||
import type { EntityDisplayComponent } from './types';
|
||||
|
||||
export let item: T;
|
||||
export let displayComponent: EntityDisplayComponent<T>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let context: Record<string, any> | undefined = undefined;
|
||||
export let displayComponent: EntityDisplayComponent<T, C>;
|
||||
export let context: C;
|
||||
|
||||
$: icon = displayComponent.getIcon?.(item, context);
|
||||
$: tags = displayComponent.getTags?.(item, context) || [];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" generics="T">
|
||||
<script lang="ts" generics="V, C">
|
||||
import { ChevronDown } from 'lucide-svelte';
|
||||
import ListSelectItem from './ListSelectItem.svelte';
|
||||
import type { EntityDisplayComponent } from './types';
|
||||
@@ -7,14 +7,15 @@
|
||||
|
||||
export let label: string = '';
|
||||
export let selectedValue: string | null = '';
|
||||
export let options: T[] = [];
|
||||
export let options: V[] = [];
|
||||
export let placeholder: string = 'Select an option...';
|
||||
export let required: boolean = false;
|
||||
export let disabled: boolean = false;
|
||||
export let error: string | null = null;
|
||||
export let onSelect: (value: string) => void;
|
||||
export let showSearch: boolean = false;
|
||||
export let displayComponent: EntityDisplayComponent<T>;
|
||||
export let displayComponent: EntityDisplayComponent<V, C>;
|
||||
export let getOptionContext: (option: V, index: number) => C = () => new Object() as C;
|
||||
|
||||
let isOpen = false;
|
||||
let dropdownElement: HTMLDivElement;
|
||||
@@ -27,12 +28,14 @@
|
||||
$: selectedItem = options.find((i) => displayComponent.getId(i) === selectedValue);
|
||||
|
||||
// Filter options based on search text
|
||||
$: filteredOptions = options.filter((option) => {
|
||||
$: filteredOptions = options.filter((option, index) => {
|
||||
if (!filterText.trim()) return true;
|
||||
|
||||
const context = getOptionContext(option, index);
|
||||
|
||||
const searchTerm = filterText.toLowerCase();
|
||||
const label = displayComponent.getLabel(option).toLowerCase();
|
||||
const description = displayComponent.getDescription?.(option)?.toLowerCase() || '';
|
||||
const description = displayComponent.getDescription?.(option, context)?.toLowerCase() || '';
|
||||
|
||||
return label.includes(searchTerm) || description.includes(searchTerm);
|
||||
});
|
||||
@@ -45,10 +48,11 @@
|
||||
return [{ category: null, options: optionsToGroup }];
|
||||
}
|
||||
|
||||
const groups = new SvelteMap<string | null, T[]>();
|
||||
const groups = new SvelteMap<string | null, V[]>();
|
||||
|
||||
optionsToGroup.forEach((option) => {
|
||||
const category = displayComponent.getCategory!(option);
|
||||
optionsToGroup.forEach((option, index) => {
|
||||
const context = getOptionContext(option, index);
|
||||
const category = displayComponent.getCategory!(option, context);
|
||||
if (!groups.has(category)) {
|
||||
groups.set(category, []);
|
||||
}
|
||||
@@ -105,11 +109,21 @@
|
||||
|
||||
function handleSelect(value: string) {
|
||||
try {
|
||||
const item = options.find((i) => displayComponent.getId(i) === value);
|
||||
if (item && !displayComponent.getIsDisabled?.(item)) {
|
||||
isOpen = false;
|
||||
filterText = '';
|
||||
onSelect(value);
|
||||
let index;
|
||||
const item = options.find((o, i) => {
|
||||
if (displayComponent.getId(o) === value) {
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (item && index) {
|
||||
const context = getOptionContext(item, index);
|
||||
if (!displayComponent.getIsDisabled?.(item, context)) {
|
||||
isOpen = false;
|
||||
filterText = '';
|
||||
onSelect(value);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error in handleSelect:', e);
|
||||
@@ -168,7 +182,8 @@
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
{#if selectedItem}
|
||||
<ListSelectItem item={selectedItem} {displayComponent} />
|
||||
{@const context = getOptionContext(selectedItem, 0)}
|
||||
<ListSelectItem {context} item={selectedItem} {displayComponent} />
|
||||
{:else}
|
||||
<span class="text-secondary"
|
||||
>{options.length == 0 ? 'No options available' : placeholder}</span
|
||||
@@ -232,6 +247,7 @@
|
||||
|
||||
<!-- Options in this category -->
|
||||
{#each group.options as option, optionIndex (displayComponent.getId(option))}
|
||||
{@const context = getOptionContext(option, optionIndex)}
|
||||
{@const isLastInGroup = optionIndex === group.options.length - 1}
|
||||
{@const isLastGroup = groupIndex === groupedOptions.length - 1}
|
||||
<button
|
||||
@@ -239,18 +255,18 @@
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!displayComponent.getIsDisabled?.(option)) {
|
||||
if (!displayComponent.getIsDisabled?.(option, context)) {
|
||||
handleSelect(displayComponent.getId(option));
|
||||
}
|
||||
}}
|
||||
class="w-full px-3 py-3 text-left transition-colors
|
||||
{!isLastInGroup || !isLastGroup ? 'border-b border-gray-600' : ''}
|
||||
{displayComponent.getIsDisabled?.(option)
|
||||
{displayComponent.getIsDisabled?.(option, context)
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
: 'hover:bg-gray-600'}"
|
||||
disabled={displayComponent.getIsDisabled?.(option)}
|
||||
disabled={displayComponent.getIsDisabled?.(option, context)}
|
||||
>
|
||||
<ListSelectItem item={option} {displayComponent} />
|
||||
<ListSelectItem {context} item={option} {displayComponent} />
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
import { getServiceForBinding, getServiceHost } from '$lib/features/services/store';
|
||||
|
||||
export const BindingWithServiceDisplay: EntityDisplayComponent<Binding> = {
|
||||
export const BindingWithServiceDisplay: EntityDisplayComponent<Binding, object> = {
|
||||
getId: (binding: Binding) => binding.id,
|
||||
getLabel: (binding: Binding) => {
|
||||
const service = get(getServiceForBinding(binding.id));
|
||||
@@ -82,6 +82,7 @@
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let item: Binding;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={BindingWithServiceDisplay} />
|
||||
<ListSelectItem {context} {item} displayComponent={BindingWithServiceDisplay} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
import { entities } from '$lib/shared/stores/metadata';
|
||||
|
||||
export const DaemonDisplay: EntityDisplayComponent<Daemon> = {
|
||||
export const DaemonDisplay: EntityDisplayComponent<Daemon, object> = {
|
||||
getId: (daemon: Daemon) => daemon.id,
|
||||
getLabel: (daemon: Daemon) => get(getHostFromId(daemon.host_id))?.name || 'Unknown Daemon',
|
||||
getDescription: (daemon: Daemon) => get(getHostFromId(daemon.host_id))?.description || '',
|
||||
@@ -21,6 +21,7 @@
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let item: Daemon;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={DaemonDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={DaemonDisplay} />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts" generics="T">
|
||||
<script lang="ts" generics="T, C">
|
||||
import ListSelectItem from '$lib/shared/components/forms/selection/ListSelectItem.svelte';
|
||||
import type { EntityDisplayComponent } from '../types';
|
||||
|
||||
export let item: T;
|
||||
export let displayComponent: EntityDisplayComponent<T>;
|
||||
export let context: C;
|
||||
export let displayComponent: EntityDisplayComponent<T, C>;
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} {displayComponent} />
|
||||
<ListSelectItem {item} {context} {displayComponent} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Host } from '$lib/features/hosts/types/base';
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
|
||||
export const HostDisplay: EntityDisplayComponent<Host> = {
|
||||
export const HostDisplay: EntityDisplayComponent<Host, object> = {
|
||||
getId: (host: Host) => host.id,
|
||||
getLabel: (host: Host) => host.name,
|
||||
getDescription: (host: Host) => get(getHostTargetString(host)) || 'Unknown Host',
|
||||
@@ -35,6 +35,7 @@
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let item: Host;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={HostDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={HostDisplay} />
|
||||
|
||||
+12
-6
@@ -2,10 +2,15 @@
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
import { getServiceForBinding } from '$lib/features/services/store';
|
||||
|
||||
export const InterfaceBindingDisplay: EntityDisplayComponent<InterfaceBinding> = {
|
||||
interface ServiceAndHost {
|
||||
service: Service;
|
||||
host: Host;
|
||||
}
|
||||
|
||||
export const InterfaceBindingDisplay: EntityDisplayComponent<InterfaceBinding, ServiceAndHost> = {
|
||||
getId: (binding: InterfaceBinding) => binding.id,
|
||||
getLabel: (binding: InterfaceBinding) => {
|
||||
const iface = get(getInterfaceFromId(binding.interface_id));
|
||||
getLabel: (binding: InterfaceBinding, context) => {
|
||||
const iface = context?.host.interfaces.find((i) => i.id == binding.interface_id);
|
||||
const interfaceFormatted = iface ? formatInterface(iface) : 'Unknown Interface';
|
||||
return interfaceFormatted;
|
||||
},
|
||||
@@ -26,7 +31,7 @@
|
||||
binding: InterfaceBinding,
|
||||
onUpdate: (updates: Partial<InterfaceBinding>) => void,
|
||||
formApi: FormApi,
|
||||
context: { service?: Service; host?: Host }
|
||||
context: ServiceAndHost
|
||||
) => {
|
||||
return {
|
||||
component: InterfaceBindingInlineEditor,
|
||||
@@ -45,7 +50,7 @@
|
||||
<script lang="ts">
|
||||
import type { EntityDisplayComponent } from '../types';
|
||||
import ListSelectItem from '../ListSelectItem.svelte';
|
||||
import { formatInterface, getInterfaceFromId } from '$lib/features/hosts/store';
|
||||
import { formatInterface } from '$lib/features/hosts/store';
|
||||
import type { InterfaceBinding, Service } from '$lib/features/services/types/base';
|
||||
import { Link2 } from 'lucide-svelte';
|
||||
import type { Host } from '$lib/features/hosts/types/base';
|
||||
@@ -54,6 +59,7 @@
|
||||
import type { FormApi } from '../../types';
|
||||
|
||||
export let item: InterfaceBinding;
|
||||
export let context: ServiceAndHost;
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={InterfaceBindingDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={InterfaceBindingDisplay} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getSubnetFromId, isContainerSubnet } from '$lib/features/subnets/store';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export const InterfaceDisplay: EntityDisplayComponent<Interface> = {
|
||||
export const InterfaceDisplay: EntityDisplayComponent<Interface, object> = {
|
||||
getId: (iface: Interface) => iface.id,
|
||||
getLabel: (iface: Interface) => (iface.name ? iface.name : 'Unnamed Interface'),
|
||||
getDescription: (iface: Interface) => {
|
||||
@@ -39,6 +39,7 @@
|
||||
import { entities } from '$lib/shared/stores/metadata';
|
||||
|
||||
export let item: Interface;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={InterfaceDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={InterfaceDisplay} />
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
import { getServiceForBinding } from '$lib/features/services/store';
|
||||
|
||||
export const PortBindingDisplay: EntityDisplayComponent<PortBinding> = {
|
||||
interface ServiceAndHost {
|
||||
service: Service;
|
||||
host: Host;
|
||||
}
|
||||
|
||||
export const PortBindingDisplay: EntityDisplayComponent<PortBinding, ServiceAndHost> = {
|
||||
getId: (binding: PortBinding) => binding.id,
|
||||
getLabel: (binding: PortBinding) => {
|
||||
const port = get(getPortFromId(binding.port_id));
|
||||
getLabel: (binding: PortBinding, context) => {
|
||||
const port = context?.host.ports.find((p) => p.id == binding.port_id);
|
||||
const iface = binding.interface_id
|
||||
? get(getInterfaceFromId(binding.interface_id))
|
||||
? context?.host.interfaces.find((i) => i.id == binding.interface_id)
|
||||
: ALL_INTERFACES;
|
||||
const portFormatted = port ? formatPort(port) : 'Unknown Port';
|
||||
const interfaceFormatted = iface ? formatInterface(iface) : 'Unknown Interface';
|
||||
@@ -30,7 +35,7 @@
|
||||
binding: PortBinding,
|
||||
onUpdate: (updates: Partial<PortBinding>) => void,
|
||||
formApi: FormApi,
|
||||
context: { service?: Service; host?: Host }
|
||||
context
|
||||
) => {
|
||||
return {
|
||||
component: Layer4BindingInlineEditor,
|
||||
@@ -49,7 +54,7 @@
|
||||
<script lang="ts">
|
||||
import type { EntityDisplayComponent } from '../types';
|
||||
import ListSelectItem from '../ListSelectItem.svelte';
|
||||
import { formatInterface, getInterfaceFromId, getPortFromId } from '$lib/features/hosts/store';
|
||||
import { formatInterface } from '$lib/features/hosts/store';
|
||||
import { formatPort } from '$lib/shared/utils/formatting';
|
||||
import type { PortBinding, Service } from '$lib/features/services/types/base';
|
||||
import { Link2 } from 'lucide-svelte';
|
||||
@@ -59,6 +64,7 @@
|
||||
import type { FormApi } from '../../types';
|
||||
|
||||
export let item: PortBinding;
|
||||
export let context: ServiceAndHost;
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={PortBindingDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={PortBindingDisplay} />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { entities, ports } from '$lib/shared/stores/metadata';
|
||||
import type { Service } from '$lib/features/services/types/base';
|
||||
|
||||
export const PortDisplay: EntityDisplayComponent<Port> = {
|
||||
export const PortDisplay: EntityDisplayComponent<Port, object> = {
|
||||
getId: (port: Port) => `${port.id}`,
|
||||
getLabel: (port: Port) => {
|
||||
let metadata = ports.getMetadata(port.type);
|
||||
@@ -59,6 +59,7 @@
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let item: Port;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={PortDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={PortDisplay} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
export const PortTypeDisplay: EntityDisplayComponent<TypeMetadata<PortTypeMetadata>> = {
|
||||
export const PortTypeDisplay: EntityDisplayComponent<TypeMetadata<PortTypeMetadata>, object> = {
|
||||
getId: (portType: TypeMetadata<PortTypeMetadata>) => portType.id,
|
||||
getLabel: (portType: TypeMetadata<PortTypeMetadata>) =>
|
||||
`${portType.metadata.number}/${portType.metadata.protocol.toLowerCase()} - ${portType.name}`,
|
||||
@@ -19,6 +19,7 @@
|
||||
import type { EntityDisplayComponent } from '../types';
|
||||
|
||||
export let item: TypeMetadata<PortTypeMetadata>;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={PortTypeDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={PortTypeDisplay} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
|
||||
export const ServiceDisplay: EntityDisplayComponent<Service> = {
|
||||
export const ServiceDisplay: EntityDisplayComponent<Service, object> = {
|
||||
getId: (service: Service) => service.id,
|
||||
getLabel: (service: Service) => service.name,
|
||||
getDescription: (service: Service) => {
|
||||
@@ -51,6 +51,7 @@
|
||||
import { matchConfidenceLabel } from '$lib/shared/types';
|
||||
|
||||
export let item: Service;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={ServiceDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={ServiceDisplay} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
export const ServiceTypeDisplay: EntityDisplayComponent<
|
||||
TypeMetadata<ServicedDefinitionMetadata>
|
||||
TypeMetadata<ServicedDefinitionMetadata>,
|
||||
object
|
||||
> = {
|
||||
getId: (serviceType: TypeMetadata<ServicedDefinitionMetadata>) => serviceType.id,
|
||||
getLabel: (serviceType: TypeMetadata<ServicedDefinitionMetadata>) => serviceType.name,
|
||||
@@ -31,6 +32,7 @@
|
||||
import type { EntityDisplayComponent } from '../types';
|
||||
|
||||
export let item: TypeMetadata<ServicedDefinitionMetadata>;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={ServiceTypeDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={ServiceTypeDisplay} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
export const SubnetDisplay: EntityDisplayComponent<Subnet> = {
|
||||
export const SubnetDisplay: EntityDisplayComponent<Subnet, object> = {
|
||||
getId: (subnet: Subnet) => subnet.id,
|
||||
getLabel: (subnet: Subnet) => subnet.name,
|
||||
getDescription: (subnet: Subnet) => (subnet.name == subnet.cidr ? '' : subnet.cidr),
|
||||
@@ -18,6 +18,7 @@
|
||||
import type { Subnet } from '$lib/features/subnets/types/base';
|
||||
|
||||
export let item: Subnet;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={SubnetDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={SubnetDisplay} />
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
import { entities, serviceDefinitions } from '$lib/shared/stores/metadata';
|
||||
|
||||
export const VirtualizationManagerServiceDisplay: EntityDisplayComponent<Service> = {
|
||||
export const VirtualizationManagerServiceDisplay: EntityDisplayComponent<Service, object> = {
|
||||
getId: (service: Service) => service.id,
|
||||
getLabel: (service: Service) => service.name,
|
||||
getDescription: (service: Service) => {
|
||||
@@ -47,6 +47,7 @@
|
||||
import { hosts } from '$lib/features/hosts/store';
|
||||
|
||||
export let item: Service;
|
||||
export let context = {};
|
||||
</script>
|
||||
|
||||
<ListSelectItem {item} displayComponent={VirtualizationManagerServiceDisplay} />
|
||||
<ListSelectItem {item} {context} displayComponent={VirtualizationManagerServiceDisplay} />
|
||||
|
||||
@@ -2,26 +2,19 @@ import type { Component } from 'svelte';
|
||||
import type { TagProps } from '../../data/types';
|
||||
import type { IconComponent } from '$lib/shared/utils/types';
|
||||
import type { FormApi } from '../types';
|
||||
|
||||
export interface EntityDisplayComponent<T> {
|
||||
// @typescript-eslint/no-explicit-any
|
||||
export interface EntityDisplayComponent<T, C> {
|
||||
// Required methods
|
||||
getId(item: T): string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getLabel(item: T, context?: Record<string, any>): string;
|
||||
getLabel(item: T, context?: C): string;
|
||||
|
||||
// Optional methods with defaults
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getDescription?(item: T, context?: Record<string, any>): string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getIcon?(item: T, context?: Record<string, any>): IconComponent | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getIconColor?(item: T, context?: Record<string, any>): string | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getTags?(item: T, context?: Record<string, any>): TagProps[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getIsDisabled?(item: T, context?: Record<string, any>): boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getCategory?(item: T, context?: Record<string, any>): string | null;
|
||||
getDescription?(item: T, context: C): string;
|
||||
getIcon?(item: T, context: C): IconComponent | null;
|
||||
getIconColor?(item: T, context: C): string | null;
|
||||
getTags?(item: T, context: C): TagProps[];
|
||||
getIsDisabled?(item: T, context: C): boolean;
|
||||
getCategory?(item: T, context: C): string | null;
|
||||
|
||||
// Optional inline editing support
|
||||
supportsInlineEdit?: boolean;
|
||||
@@ -29,15 +22,10 @@ export interface EntityDisplayComponent<T> {
|
||||
item: T,
|
||||
onUpdate: (updates: Partial<T>) => void,
|
||||
formApi: FormApi,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
context?: Record<string, any>
|
||||
context?: C
|
||||
): {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
component: Component<any>;
|
||||
props: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DisplayComponentProps<T> {
|
||||
item: T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user