diff --git a/infrastructure/control-panel/.storybook/main.ts b/infrastructure/control-panel/.storybook/main.ts index 0bd00f92..82ae9232 100644 --- a/infrastructure/control-panel/.storybook/main.ts +++ b/infrastructure/control-panel/.storybook/main.ts @@ -1,25 +1,20 @@ import type { StorybookConfig } from '@storybook/sveltekit'; -import { join, dirname } from "path" +import { join, dirname } from 'path'; /** -* This function is used to resolve the absolute path of a package. -* It is needed in projects that use Yarn PnP or are set up within a monorepo. -*/ + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ function getAbsolutePath(value: string): any { - return dirname(require.resolve(join(value, 'package.json'))) + return dirname(require.resolve(join(value, 'package.json'))); } const config: StorybookConfig = { - "stories": [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|ts|svelte)" - ], - "addons": [ - getAbsolutePath('@storybook/addon-svelte-csf') - ], - "framework": { - "name": getAbsolutePath('@storybook/sveltekit'), - "options": {} - } + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], + addons: [getAbsolutePath('@storybook/addon-svelte-csf')], + framework: { + name: getAbsolutePath('@storybook/sveltekit'), + options: {} + } }; -export default config; \ No newline at end of file +export default config; diff --git a/infrastructure/control-panel/.storybook/preview.ts b/infrastructure/control-panel/.storybook/preview.ts index 82ddc5b9..c79f4098 100644 --- a/infrastructure/control-panel/.storybook/preview.ts +++ b/infrastructure/control-panel/.storybook/preview.ts @@ -1,15 +1,15 @@ -import type { Preview } from '@storybook/sveltekit' -import "../src/app.css"; +import type { Preview } from '@storybook/sveltekit'; +import '../src/app.css'; const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i + } + } + } }; -export default preview; \ No newline at end of file +export default preview; diff --git a/infrastructure/control-panel/package.json b/infrastructure/control-panel/package.json index 6d5f8162..fc478f2c 100644 --- a/infrastructure/control-panel/package.json +++ b/infrastructure/control-panel/package.json @@ -42,6 +42,14 @@ "vite": "^7.0.4" }, "dependencies": { - "@inlang/paraglide-js": "^2.0.0" + "@hugeicons/core-free-icons": "^1.0.13", + "@hugeicons/svelte": "^1.0.2", + "@inlang/paraglide-js": "^2.0.0", + "@xyflow/svelte": "^1.2.2", + "clsx": "^2.1.1", + "flowbite": "^3.1.2", + "flowbite-svelte": "^1.10.7", + "flowbite-svelte-icons": "^2.2.1", + "tailwind-merge": "^3.0.2" } } diff --git a/infrastructure/control-panel/src/app.css b/infrastructure/control-panel/src/app.css index 439455e7..2c0d12d5 100644 --- a/infrastructure/control-panel/src/app.css +++ b/infrastructure/control-panel/src/app.css @@ -1,36 +1,71 @@ @import 'tailwindcss'; +@font-face { + font-family: 'Archivo'; + src: url('/fonts/Archivo-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; +} + +@layer base { + /* Typography */ + h1 { + @apply text-[90px]/[1.5] font-semibold text-black; + } + + h2 { + @apply text-6xl/[1.5] font-semibold text-black; + } + + h3 { + @apply text-3xl/[1.5] font-semibold text-black; + } + + h4 { + @apply text-xl/[1.5] font-semibold text-black; + } + + p { + @apply text-base/[1.5] font-normal text-black; + } + + .small { + @apply text-xs/[1.5] font-normal text-black; + } +} @theme { - /* Custom theme */ - --color-primary: #8e52ff; - --color-primary-100: #e8dcff; - --color-primary-200: #d2baff; - --color-primary-300: #bb97ff; - --color-primary-400: #a575ff; - --color-primary-500: #8e52ff; - - --color-secondary: #73efd5; - --color-secondary-100: #e3fcf7; - --color-secondary-200: #c7f9ee; - --color-secondary-300: #abf6e6; - --color-secondary-400: #8ff2dd; - --color-secondary-500: #73efd5; - - --color-white: #ffffff; - --color-gray: #f5f5f5; - - --color-black: #1f1f1f; - --color-black-100: #d2d2d2; - --color-black-300: #a5a5a5; - --color-black-500: #797979; - --color-black-700: #4c4c4c; - --color-black-900: #1f1f1f; - - --color-danger: #ff5255; - --color-danger-100: #ffdcdd; - --color-danger-200: #ffb1a7; - --color-danger-300: #ff968e; - --color-danger-400: #ff7b77; - --color-danger-500: #ff5255; -} \ No newline at end of file + /* Custom theme */ + --color-primary: #8e52ff; + --color-primary-100: #e8dcff; + --color-primary-200: #d2baff; + --color-primary-300: #bb97ff; + --color-primary-400: #a575ff; + --color-primary-500: #8e52ff; + + --color-secondary: #73efd5; + --color-secondary-100: #e3fcf7; + --color-secondary-200: #c7f9ee; + --color-secondary-300: #abf6e6; + --color-secondary-400: #8ff2dd; + --color-secondary-500: #73efd5; + + --color-white: #ffffff; + --color-gray: #f5f5f5; + + --color-black: #1f1f1f; + --color-black-100: #d2d2d2; + --color-black-300: #a5a5a5; + --color-black-500: #797979; + --color-black-700: #4c4c4c; + --color-black-900: #1f1f1f; + + --color-danger: #ff5255; + --color-danger-100: #ffdcdd; + --color-danger-200: #ffb1a7; + --color-danger-300: #ff968e; + --color-danger-400: #ff7b77; + --color-danger-500: #ff5255; + + --color-green: #0fb340; +} diff --git a/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.stories.ts b/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.stories.ts new file mode 100644 index 00000000..d8bbb848 --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.stories.ts @@ -0,0 +1,21 @@ +import type { ComponentProps } from 'svelte'; +import { LiveDataFlow } from '..'; + +export default { + title: 'UI/LiveDataFlow', + component: LiveDataFlow, + tags: ['autodocs'], + render: (args: { Component: LiveDataFlow; props: ComponentProps }) => ({ + Component: LiveDataFlow, + props: args + }) +}; + +export const Default = { + args: { + events: [ + { id: 1, from: 'alice', to: 'pictique' }, + { id: 2, from: 'pictique', to: 'bob' } + ] + } +}; diff --git a/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.svelte b/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.svelte new file mode 100644 index 00000000..a8ef518a --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/LiveDataFlow/LiveDataFlow.svelte @@ -0,0 +1,100 @@ + + +
+
+

Live Monitoring

+ +
+
+ +
+
+
+ +
+ +
{events[0].from}
+
{events[0].vaultName}
+
+ +
+ Icon +
{events[1].from}
+
+ +
+ +
{events[2].from}
+
{events[2].vaultName}
+
+
+
+ + diff --git a/infrastructure/control-panel/src/lib/fragments/Logs/Logs.stories.ts b/infrastructure/control-panel/src/lib/fragments/Logs/Logs.stories.ts new file mode 100644 index 00000000..7c192844 --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/Logs/Logs.stories.ts @@ -0,0 +1,37 @@ +import type { ComponentProps } from 'svelte'; +import Logs from './Logs.svelte'; + +export default { + title: 'UI/Logs', + component: Logs, + tags: ['autodocs'], + render: (args: { Component: Logs; props: ComponentProps }) => ({ + Component: Logs, + props: args + }) +}; + +export const Default = { + args: { + events: [ + { + timestamp: new Date(), + action: 'upload', + message: 'msg_123', + to: 'alice.vault.dev' + }, + { + timestamp: new Date(), + action: 'fetch', + message: 'msg_124', + from: 'bob.vault.dev' + }, + { + timestamp: new Date(), + action: 'webhook', + to: 'Alice', + from: 'Pic' + } + ] + } +}; diff --git a/infrastructure/control-panel/src/lib/fragments/Logs/Logs.svelte b/infrastructure/control-panel/src/lib/fragments/Logs/Logs.svelte new file mode 100644 index 00000000..b22dc257 --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/Logs/Logs.svelte @@ -0,0 +1,54 @@ + + +
+

Logs

+ {#each events as event, i} +
{ + activeEventIndex = i; + }} + > +

[{parseTimestamp(event.timestamp)}]

+
+ + {capitalizeFirstLetter(event.action)} + {event.action === 'webhook' ? 'triggered' : ''} + + {#if event.action === 'upload'} + → {event.to} + {:else if event.action === 'fetch'} + ← {event.from} + {:else if event.action === 'webhook'} + ({event.from} → {event.to}) + {/if} + {#if event.action === 'upload' || event.action === 'fetch'} +
+ ({event.message}) + {/if} +
+
+ {/each} +
diff --git a/infrastructure/control-panel/src/lib/fragments/Nodes/VaultNode.svelte b/infrastructure/control-panel/src/lib/fragments/Nodes/VaultNode.svelte new file mode 100644 index 00000000..f4d51933 --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/Nodes/VaultNode.svelte @@ -0,0 +1,79 @@ + + +
+ +
+ +
+
{data.label}
+
{data.subLabel}
+
+
+ + +
+ + diff --git a/infrastructure/control-panel/src/lib/fragments/TableCard/TableCard.svelte b/infrastructure/control-panel/src/lib/fragments/TableCard/TableCard.svelte new file mode 100644 index 00000000..96c51c0d --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/TableCard/TableCard.svelte @@ -0,0 +1,9 @@ + + +
+ {@render children?.()} +
diff --git a/infrastructure/control-panel/src/lib/fragments/TableCardHeader/TableCardHeader.svelte b/infrastructure/control-panel/src/lib/fragments/TableCardHeader/TableCardHeader.svelte new file mode 100644 index 00000000..0f9ed80e --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/TableCardHeader/TableCardHeader.svelte @@ -0,0 +1,37 @@ + + +
+
+

{title}

+
+ + + + +
+
+ +
+ +

{rightTitle}

+
+
diff --git a/infrastructure/control-panel/src/lib/fragments/index.ts b/infrastructure/control-panel/src/lib/fragments/index.ts new file mode 100644 index 00000000..98ea6de0 --- /dev/null +++ b/infrastructure/control-panel/src/lib/fragments/index.ts @@ -0,0 +1,5 @@ +export { default as LiveDataFlow } from './LiveDataFlow/LiveDataFlow.svelte'; +export { default as Logs } from './Logs/Logs.svelte'; +export { default as VaultNode } from './Nodes/VaultNode.svelte'; +export { default as TableCard } from './TableCard/TableCard.svelte'; +export { default as TableCardHeader } from './TableCardHeader/TableCardHeader.svelte'; diff --git a/infrastructure/control-panel/src/lib/types/index.ts b/infrastructure/control-panel/src/lib/types/index.ts new file mode 100644 index 00000000..02fb43b9 --- /dev/null +++ b/infrastructure/control-panel/src/lib/types/index.ts @@ -0,0 +1 @@ +export * from './log.types'; diff --git a/infrastructure/control-panel/src/lib/types/log.types.ts b/infrastructure/control-panel/src/lib/types/log.types.ts new file mode 100644 index 00000000..0d4b8c05 --- /dev/null +++ b/infrastructure/control-panel/src/lib/types/log.types.ts @@ -0,0 +1,22 @@ +export interface UploadEvent { + timestamp: Date; + action: 'upload'; + message: string; + to: string; +} + +export interface FetchEvent { + timestamp: Date; + action: 'fetch'; + message: string; + from: string; +} + +export interface WebhookEvent { + timestamp: Date; + action: 'webhook'; + from: string; + to: string; +} + +export type LogEvent = UploadEvent | FetchEvent | WebhookEvent; diff --git a/infrastructure/control-panel/src/lib/ui/Button/Button.stories.snippet.svelte b/infrastructure/control-panel/src/lib/ui/Button/Button.stories.snippet.svelte new file mode 100644 index 00000000..5dec60ae --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/Button.stories.snippet.svelte @@ -0,0 +1,24 @@ + + +{#snippet ButtonText()} + Button +{/snippet} + +{#snippet ButtonNavText()} + Nav Button +{/snippet} + +{#snippet ButtonNavSettings()} +
+
+ +
+

Settings

+
+ +{/snippet} diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.stories.ts b/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.stories.ts new file mode 100644 index 00000000..a561eed6 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.stories.ts @@ -0,0 +1,43 @@ +import type { ComponentProps } from 'svelte'; +import { ButtonText } from './Button.stories.snippet.svelte'; +import ButtonAction from './ButtonAction.svelte'; + +export default { + title: 'UI/ButtonAction', + component: ButtonAction, + tags: ['autodocs'], + render: (args: { Component: ButtonAction; props: ComponentProps }) => ({ + Component: ButtonAction, + props: args + }) +}; + +export const Solid = { + args: { variant: 'solid', children: ButtonText } +}; + +export const Soft = { + args: { variant: 'soft', children: ButtonText } +}; + +export const Danger = { + args: { variant: 'danger', children: ButtonText } +}; + +export const DangerSoft = { + args: { variant: 'danger-soft', children: ButtonText } +}; + +export const Loading = { + args: { isLoading: true, children: ButtonText } +}; + +export const BlockingClick = { + args: { + blockingClick: true, + children: ButtonText, + callback: async () => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + } +}; diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.svelte b/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.svelte new file mode 100644 index 00000000..d0e033d8 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonAction.svelte @@ -0,0 +1,131 @@ + + + + + diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.stories.ts b/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.stories.ts new file mode 100644 index 00000000..517ace15 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.stories.ts @@ -0,0 +1,75 @@ +import { FlashlightIcon, ViewIcon } from '@hugeicons/core-free-icons'; +import type { ComponentProps } from 'svelte'; +import ButtonIcon from './ButtonIcon.svelte'; + +export default { + title: 'UI/ButtonIcon', + component: ButtonIcon, + tags: ['autodocs'], + render: (args: ComponentProps) => ({ + Component: ButtonIcon, + props: args + }) +}; + +export const Default = { + render: () => ({ + Component: ButtonIcon, + props: { + ariaLabel: 'Default button', + bgSize: 'md', // Predefined size + iconSize: 'md', + icon: ViewIcon, + bgColor: 'black', + iconColor: 'white' + } + }) +}; + +export const CustomSize = { + render: () => ({ + Component: ButtonIcon, + props: { + ariaLabel: 'Custom sized button', + bgSize: 'w-[120px] h-[120px]', // Custom Tailwind size + iconSize: 56, // Custom pixel size + icon: FlashlightIcon, + bgColor: 'bg-danger', + iconColor: 'white' + } + }) +}; + +export const Loading = { + render: () => ({ + Component: ButtonIcon, + props: { + ariaLabel: 'Loading button', + bgSize: 'md', + iconSize: 'md', + icon: FlashlightIcon, + isLoading: true, + bgColor: 'black', + iconColor: 'white' + } + }) +}; + +export const WithCallback = { + render: () => ({ + Component: ButtonIcon, + props: { + ariaLabel: 'Button with async callback', + bgSize: 'md', + iconSize: 'md', + icon: FlashlightIcon, + callback: async () => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + console.log('Action completed!'); + }, + blockingClick: true, + bgColor: 'primary', + iconColor: 'white' + } + }) +}; diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.svelte b/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.svelte new file mode 100644 index 00000000..7b5877f3 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonIcon.svelte @@ -0,0 +1,179 @@ + + + + + diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.stories.ts b/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.stories.ts new file mode 100644 index 00000000..ad85c58e --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.stories.ts @@ -0,0 +1,25 @@ +import type { ComponentProps } from 'svelte'; +import { ButtonNavSettings, ButtonNavText } from './Button.stories.snippet.svelte'; +import ButtonNav from './ButtonNav.svelte'; + +export default { + title: 'UI/ButtonNav', + component: ButtonNav, + tags: ['autodocs'], + render: (args: { Component: ButtonNav; props: ComponentProps }) => ({ + Component: ButtonNav, + props: args + }) +}; + +export const Default = { + args: { href: '#', children: ButtonNavText } +}; + +export const ForSettings = { + args: { + href: '#', + children: ButtonNavSettings, + class: 'flex items-center justify-between px-3 py-2' + } +}; diff --git a/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.svelte b/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.svelte new file mode 100644 index 00000000..1b760885 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/ButtonNav.svelte @@ -0,0 +1,40 @@ + + + + {@render children()} + + + diff --git a/infrastructure/control-panel/src/lib/ui/Button/index.ts b/infrastructure/control-panel/src/lib/ui/Button/index.ts new file mode 100644 index 00000000..e89f9d87 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Button/index.ts @@ -0,0 +1,5 @@ +import Action from './ButtonAction.svelte'; +import Icon from './ButtonIcon.svelte'; +import Nav from './ButtonNav.svelte'; + +export { Action, Icon, Nav }; diff --git a/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.stories.ts b/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.stories.ts new file mode 100644 index 00000000..7c711f77 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.stories.ts @@ -0,0 +1,20 @@ +import type { ComponentProps } from 'svelte'; +import Checkbox from './Checkbox.svelte'; + +export default { + title: 'UI/Checkbox', + component: Checkbox, + tags: ['autodocs'], + render: (args: { Component: Checkbox; props: ComponentProps }) => ({ + Component: Checkbox, + props: args + }) +}; + +export const Basic = { + args: {} +}; + +export const Disabled = { + args: { disabled: true } +}; diff --git a/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.svelte b/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.svelte new file mode 100644 index 00000000..eea2ed30 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Checkbox/Checkbox.svelte @@ -0,0 +1,51 @@ + + + { + const target = e.target as HTMLInputElement; + checked = target.checked; + onchange(checked); + }} +/> + + inputElement?.click()} +> + {#if checked} + + {/if} + diff --git a/infrastructure/control-panel/src/lib/ui/Table/Table.stories.ts b/infrastructure/control-panel/src/lib/ui/Table/Table.stories.ts new file mode 100644 index 00000000..e940592b --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Table/Table.stories.ts @@ -0,0 +1,114 @@ +import Table from './Table.svelte'; + +export default { + title: 'UI/Table', + component: Table, + tags: ['autodocs'], + render: (args: { Component: Table }) => ({ + Component: Table, + props: args + }) +}; +const handlePreviousPage = async () => { + alert('Previous btn clicked. Make a call to your server to fetch data.'); +}; +const handleNextPage = async () => { + alert('Next btn clicked. Make a call to your server to fetch data.'); +}; +const tableHeadings = [ + 'Image', + 'Material Name', + 'Description', + 'Product ID', + 'Smart Contract' + // "Ledger Link", +]; +const pages = [ + { name: '1', href: '#' }, + { name: '2', href: '#' }, + { name: '3', href: '#' } +]; +const tableData = [ + { + image: 'https://example.com/image1.jpg', + name: 'Material 1', + description: 'Description of Material 1', + productId: '12345', + smartContract: '0x1234567890abcdef', + ledgerLink: 'https://example.com/ledger1' + }, + { + image: 'https://example.com/image2.jpg', + name: 'Material 2', + description: 'Description of Material 2', + productId: '67890', + smartContract: '0xabcdef1234567890', + ledgerLink: 'https://example.com/ledger2' + }, + { + image: 'https://example.com/image3.jpg', + name: 'Material 3', + description: 'Description of Material 3', + productId: '54321', + smartContract: '0x0987654321fedcba', + ledgerLink: 'https://example.com/ledger3' + }, + { + image: 'https://example.com/image4.jpg', + name: 'Material 4', + description: 'Description of Material 4', + productId: '11223', + smartContract: '0xabcdef0987654321', + ledgerLink: 'https://example.com/ledger4' + }, + { + image: 'https://example.com/image5.jpg', + name: 'Material 5', + description: 'Description of Material 5', + productId: '44556', + smartContract: '0x123456abcdef7890', + ledgerLink: 'https://example.com/ledger5' + } +]; + +const mappedData = tableData.map((row) => { + return { + rowOne: { + type: 'image', + value: row.image + }, + rowTwo: { + type: 'text', + value: row.name + }, + rowThree: { + type: 'text', + value: row.description + }, + rowFour: { + type: 'text', + value: row.productId + }, + rowFive: { + type: 'text', + value: row.smartContract + } + // rowSix: { + // type: "snippet", + // snippet: BadgeCell, + // value: + // row.ledgerLink, + // }, + }; +}); + +export const Primary = { + args: { + tableHeadings, + tableData: mappedData, + withSelection: true, + pages, + handlePreviousPage, + handleNextPage + } +}; diff --git a/infrastructure/control-panel/src/lib/ui/Table/Table.svelte b/infrastructure/control-panel/src/lib/ui/Table/Table.svelte new file mode 100644 index 00000000..d77dc858 --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/Table/Table.svelte @@ -0,0 +1,534 @@ + + + + + {#if withSelection} + + toggleCheckAll(e as boolean)} /> + + {/if} + {#each tableHeadings as tableHeading, i} + {@render HeadCell(tableHeading, i)} + {/each} + {#if isScrollable} + + {/if} + +
+
+ + + {#each tableData as data, i} + { + selectedRow = i; + handleSelectedRow && handleSelectedRow(i); + }} + class="hover:bg-gray w-full bg-white select-none + {selectedRow === i && 'bg-gray!'}" + > + {#if withSelection} + + {/if} + {#each tableHeadings as field, j} + {@render BodyCell(data, field, j)} + {/each} + + {/each} + +
+ toggleCheckItem(i, e as boolean)} + /> +
+
+ +{#snippet HeadCell(heading: string, i: number)} + {@const cellData = tableData[0]?.[heading]} + {@const isSortable = cellData && 'sortable' in cellData && cellData.sortable} + + {#if heading.startsWith('_') || heading === ''} + +
+ {:else if isSortable && onSort} + + {:else} + +
+ {heading} +
+ {/if} +
+{/snippet} + +{#snippet BodyCell(data: Record, field: string, i: number)} + + {#if 'snippet' in data[field]} + {@const snippet = data[field].snippet} + {@const value = data[field].value as T} + {#if snippet} + {@render snippet(value)} + {/if} + {:else if 'type' in data[field] && data[field].type === 'image' && typeof data[field].value === 'string'} + + + {:else if 'type' in data[field] && data[field].type === 'link'} + + {data[field].value} + + {:else if 'type' in data[field] && data[field].type === 'text'} + {data[field].value} + {:else} + {data[field].value} + {/if} + +{/snippet} + + +{#if withPagination} +
+

+ Showing {(currentPage - 1) * pageSize + 1} - {Math.min( + currentPage * pageSize, + totalItems + )} of {totalItems} +

+
+ +
+ + + + + {#each generatePageNumbers(currentPage, totalPages) as pageNum} + {#if pageNum === '...'} + ... + {:else} + + {/if} + {/each} + + + +
+
+
+{/if} + + + + diff --git a/infrastructure/control-panel/src/lib/ui/index.ts b/infrastructure/control-panel/src/lib/ui/index.ts new file mode 100644 index 00000000..71065e4f --- /dev/null +++ b/infrastructure/control-panel/src/lib/ui/index.ts @@ -0,0 +1,3 @@ +export { default as ButtonAction } from './Button/ButtonAction.svelte'; +export { default as Table } from './Table/Table.svelte'; +export { default as Checkbox } from './Checkbox/Checkbox.svelte'; diff --git a/infrastructure/control-panel/src/lib/utils/index.ts b/infrastructure/control-panel/src/lib/utils/index.ts new file mode 100644 index 00000000..256d4a24 --- /dev/null +++ b/infrastructure/control-panel/src/lib/utils/index.ts @@ -0,0 +1,3 @@ +export * from './mergeClasses'; +export * from './parseDates'; +export * from './typography'; diff --git a/infrastructure/control-panel/src/lib/utils/mergeClasses.ts b/infrastructure/control-panel/src/lib/utils/mergeClasses.ts new file mode 100644 index 00000000..256f86ff --- /dev/null +++ b/infrastructure/control-panel/src/lib/utils/mergeClasses.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/infrastructure/control-panel/src/lib/utils/parseDates.ts b/infrastructure/control-panel/src/lib/utils/parseDates.ts new file mode 100644 index 00000000..69282fdd --- /dev/null +++ b/infrastructure/control-panel/src/lib/utils/parseDates.ts @@ -0,0 +1,3 @@ +export const parseTimestamp = (timestamp: Date) => { + return `${timestamp.getHours().toString().padStart(2, '0')}:${timestamp.getMinutes().toString().padStart(2, '0')}:${timestamp.getSeconds().toString().padStart(2, '0')}`; +}; diff --git a/infrastructure/control-panel/src/lib/utils/typography.ts b/infrastructure/control-panel/src/lib/utils/typography.ts new file mode 100644 index 00000000..4a6227c4 --- /dev/null +++ b/infrastructure/control-panel/src/lib/utils/typography.ts @@ -0,0 +1,3 @@ +export const capitalizeFirstLetter = (val: string) => { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); +}; diff --git a/infrastructure/control-panel/src/routes/+layout.svelte b/infrastructure/control-panel/src/routes/+layout.svelte index b93e9bae..dc43ad8f 100644 --- a/infrastructure/control-panel/src/routes/+layout.svelte +++ b/infrastructure/control-panel/src/routes/+layout.svelte @@ -1,7 +1,49 @@ -{@render children()} +
+
+

Control Panel

+ {#if pageUrl === '/'} +
+ + Refresh + + + + + goto('/monitoring')}>Start Monitoring +
+ {:else} + goto('/')}>Exit Monitoring + {/if} +
+
+ {@render children()} +
+
diff --git a/infrastructure/control-panel/src/routes/+page.svelte b/infrastructure/control-panel/src/routes/+page.svelte index cc88df0e..195945ae 100644 --- a/infrastructure/control-panel/src/routes/+page.svelte +++ b/infrastructure/control-panel/src/routes/+page.svelte @@ -1,2 +1,129 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + +
+ + + + + + + +
+ + diff --git a/infrastructure/control-panel/src/routes/monitoring/+page.svelte b/infrastructure/control-panel/src/routes/monitoring/+page.svelte new file mode 100644 index 00000000..c308041e --- /dev/null +++ b/infrastructure/control-panel/src/routes/monitoring/+page.svelte @@ -0,0 +1,249 @@ + + +
+
+
+

Live Monitoring

+
+ { + isModalOpen = !isModalOpen; + }}>+ Add Vault + +
+
+ + {#if SvelteFlowComponent} +
+ +
+ {:else} +
+ Loading flow chart... +
+ {/if} +
+ +
+ + +

Search vaults

+ + +
    + {#each availableVaults as vault (vault.id)} +
  • + + + +
  • + {/each} +
+ Add Voults +
+ + diff --git a/infrastructure/control-panel/static/fonts/Archivo-VariableFont_wdth,wght.ttf b/infrastructure/control-panel/static/fonts/Archivo-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..9edfc8fb Binary files /dev/null and b/infrastructure/control-panel/static/fonts/Archivo-VariableFont_wdth,wght.ttf differ diff --git a/platforms/eVoting/next-env.d.ts b/platforms/eVoting/next-env.d.ts new file mode 100644 index 00000000..1b3be084 --- /dev/null +++ b/platforms/eVoting/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/platforms/group-charter-manager/next-env.d.ts b/platforms/group-charter-manager/next-env.d.ts new file mode 100644 index 00000000..1b3be084 --- /dev/null +++ b/platforms/group-charter-manager/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/platforms/pictique/src/lib/fragments/ActionMenu/ActionMenu.svelte b/platforms/pictique/src/lib/fragments/ActionMenu/ActionMenu.svelte index e2b5e35b..63e62b01 100644 --- a/platforms/pictique/src/lib/fragments/ActionMenu/ActionMenu.svelte +++ b/platforms/pictique/src/lib/fragments/ActionMenu/ActionMenu.svelte @@ -54,8 +54,7 @@