mirror of
https://github.com/KevinMidboe/infra-map.git
synced 2025-10-29 17:40:28 +00:00
global search component
This commit is contained in:
185
src/lib/components/GlobalSearch.svelte
Normal file
185
src/lib/components/GlobalSearch.svelte
Normal file
@@ -0,0 +1,185 @@
|
||||
<script lang="ts">
|
||||
import { goto, pushState } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import Dialog from './Dialog.svelte';
|
||||
import Input from './Input.svelte';
|
||||
|
||||
let open = $state(false);
|
||||
const className = 'search-container';
|
||||
|
||||
/* search & filter */
|
||||
let filterString = $state('');
|
||||
let focusIndex = $state(0);
|
||||
let children = $state([]);
|
||||
|
||||
let filteredchildren = $derived.by(() => {
|
||||
return children.filter((a) => a?.name.toLowerCase().includes(filterString));
|
||||
});
|
||||
|
||||
const updateFocus = (index: number) => {
|
||||
if (index < 0) index = 0;
|
||||
else if (index >= filteredchildren.length) {
|
||||
index = filteredchildren.length - 1;
|
||||
}
|
||||
|
||||
focusIndex = index;
|
||||
};
|
||||
|
||||
/* setup & register */
|
||||
const toggleSearchDialog = () => (open = !open);
|
||||
const focusSearchInput = () => {
|
||||
setTimeout(() => {
|
||||
const input = document.getElementsByClassName(className)[0]?.getElementsByTagName('input')[0];
|
||||
|
||||
input.focus();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
function registerShortcutKey() {
|
||||
document.addEventListener('keydown', function (event) {
|
||||
// listen for open/close command + k
|
||||
if ((event.metaKey && event.key === 'k') || (event.ctrlKey && event.key === 'k')) {
|
||||
event.preventDefault();
|
||||
console.log('Command + K / Ctrl + K was pressed');
|
||||
|
||||
toggleSearchDialog();
|
||||
|
||||
// initial state
|
||||
if (open) {
|
||||
focusSearchInput();
|
||||
filterString = '';
|
||||
updateFocus(0);
|
||||
children = window?.elements || [{ name: 'empty' }];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// listen for text, any letter should reset focusIndex
|
||||
const singleLetter = (event.key.length == 1 && event.key.match(/\D/)) || 0 > 0;
|
||||
if (open && singleLetter) {
|
||||
updateFocus(0);
|
||||
}
|
||||
|
||||
// listen for number as shortcut actions
|
||||
const digit = event.key.match(/\d/)?.[0];
|
||||
if (open && digit?.length && digit?.length > 0) {
|
||||
setTimeout(() => {
|
||||
filterString = String(filterString)?.replaceAll(digit, '');
|
||||
}, 1);
|
||||
|
||||
updateFocus(Number(digit) - 1);
|
||||
}
|
||||
|
||||
// listen for arrow keys
|
||||
if (open && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
||||
const direction = event.key === 'ArrowDown' ? 1 : -1;
|
||||
updateFocus(focusIndex + 1 * direction);
|
||||
}
|
||||
|
||||
// listen for enter key
|
||||
if (open && event.key === 'Enter' && filteredchildren.length > 0) {
|
||||
const { link } = filteredchildren[focusIndex];
|
||||
toggleSearchDialog();
|
||||
|
||||
if (String(link)?.startsWith('/')) {
|
||||
goto(link);
|
||||
} else {
|
||||
window.open(link, '_blank');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
registerShortcutKey();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<Dialog on:close={() => (open = false)} title="Search on page" description="">
|
||||
<div class={className}>
|
||||
<Input label="" bind:value={filterString} placeholder="attribute" />
|
||||
|
||||
<ul>
|
||||
{#each filteredchildren as element, index (element?.name)}
|
||||
<li class={index === focusIndex ? 'focus' : ''}>
|
||||
<h3>{element?.name}</h3>
|
||||
|
||||
<div>
|
||||
{#each Object.entries(element) as [key, value] (key)}
|
||||
<span><b>{key}:</b> {value}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</Dialog>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.search-container {
|
||||
width: 700px;
|
||||
position: relative;
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
max-height: calc(50vh);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-y: scroll;
|
||||
max-height: calc(100vh - var(--offset-top) * 4);
|
||||
max-height: 75vh;
|
||||
}
|
||||
|
||||
li {
|
||||
border: var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.focus,
|
||||
&:hover {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: calc(100% - 2px);
|
||||
height: calc(100% - 2px);
|
||||
border: 2px solid var(--theme);
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1rem;
|
||||
row-gap: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.search-container .label-input) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
overflow: unset;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
import GlobalSearch from '$lib/components/GlobalSearch.svelte';
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
@@ -13,6 +14,8 @@
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<GlobalSearch />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import PageElement from '$lib/components/PageElement.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let elems = [];
|
||||
let counter = 0;
|
||||
@@ -28,10 +29,10 @@
|
||||
return {
|
||||
bgColor: colors[counter - 1][0],
|
||||
color: colors[counter - 1][1],
|
||||
title,
|
||||
name: title,
|
||||
header: null,
|
||||
description: description ? description : '',
|
||||
link: title
|
||||
link: `/${title}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,6 +60,8 @@
|
||||
|
||||
elems = elems.concat(createPageElement('cluster '));
|
||||
elems = elems.concat(createPageElement('health '));
|
||||
|
||||
onMount(() => window.elements = elems)
|
||||
</script>
|
||||
|
||||
<PageHeader>Welcome to schleppe.cloud infra overview</PageHeader>
|
||||
@@ -80,11 +83,11 @@
|
||||
</p>
|
||||
|
||||
<div class="shortcut-grid">
|
||||
{#each elems as shortcut (shortcut.title)}
|
||||
{#each elems as shortcut (shortcut.name)}
|
||||
<PageElement
|
||||
bgColor={shortcut.bgColor}
|
||||
color={shortcut.color}
|
||||
title={shortcut.title}
|
||||
title={shortcut.name}
|
||||
header={shortcut.header}
|
||||
description={shortcut.description}
|
||||
link={shortcut.link}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Node from '$lib/components/Node.svelte';
|
||||
import Deploy from '$lib/components/Deploy.svelte';
|
||||
import Daemon from '$lib/components/Daemon.svelte';
|
||||
@@ -14,10 +15,24 @@
|
||||
const rawDaemons: V1DaemonSet[] = data?.daemons;
|
||||
const rawNodes: V1Node[] = data?.nodes;
|
||||
|
||||
let filterLC = $derived(filterValue.toLowerCase())
|
||||
let filterLC = $derived(filterValue.toLowerCase());
|
||||
let deployments = $derived(rawDeployments.filter((d) => d.metadata.name.includes(filterLC)));
|
||||
let daemons = $derived(rawDaemons.filter((d) => d.metadata.name.includes(filterLC)));
|
||||
let nodes = $derived(rawNodes.filter((n) => n.metadata.name.includes(filterLC)));
|
||||
|
||||
onMount(() => {
|
||||
console.log(deployments);
|
||||
|
||||
window.elements = deployments
|
||||
.map((d) => {
|
||||
return {
|
||||
name: d.metadata?.name || undefined,
|
||||
link: `/cluster/deployment/${d.metadata?.uid}`,
|
||||
...d
|
||||
};
|
||||
})
|
||||
.filter((d) => d.name);
|
||||
});
|
||||
</script>
|
||||
|
||||
<PageHeader>Cluster overview</PageHeader>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import ServerComp from '$lib/components/Server.svelte';
|
||||
import ServerSummary from '$lib/components/ServerSummary.svelte';
|
||||
@@ -7,6 +8,34 @@
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { cluster, nodes } = data;
|
||||
const allVms: Array<VM> = nodes.flatMap((n) => n.vms).filter((v) => v.template !== 1);
|
||||
const allLxcs: Array<LXC> = nodes.flatMap((n) => n.lxcs);
|
||||
|
||||
onMount(() => {
|
||||
window.elements = [
|
||||
...allVms
|
||||
.map((vm) => {
|
||||
return {
|
||||
link: `/servers/vm/${vm.name}`,
|
||||
...vm
|
||||
};
|
||||
})
|
||||
.filter((d) => d.name),
|
||||
...nodes.map(node => {
|
||||
return {
|
||||
link: `/servers/node/${node.name}`,
|
||||
...node
|
||||
}
|
||||
}),
|
||||
...allLxcs.map(lxc => {
|
||||
return {
|
||||
link: `/servers/lxc/${lxc.name}`,
|
||||
...lxc
|
||||
}
|
||||
})
|
||||
];
|
||||
});
|
||||
console.log(allLxcs)
|
||||
</script>
|
||||
|
||||
<PageHeader>Servers</PageHeader>
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
import FormSite from '$lib/components/forms/FormSite.svelte';
|
||||
import ThumbnailButton from '$lib/components/ThumbnailButton.svelte';
|
||||
import type { Site } from '$lib/interfaces/site.ts';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { data }: { data: { site: Site } } = $props();
|
||||
let open = $state(false);
|
||||
|
||||
const { sites } = data;
|
||||
|
||||
onMount(() => window.elements = sites)
|
||||
</script>
|
||||
|
||||
<PageHeader>Sites
|
||||
|
||||
<button class="add-site-btn affirmative" on:click={() => (open = true)}><span>Add new site</span></button>
|
||||
</PageHeader>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user