A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: list projects page

Brooke efcc7452 2b9e1763

+129 -2
+36
packages/panel/src/lib/component/StatusIcon.svelte
··· 1 + <script lang="ts" module> 2 + export type LuminaryStatus = components["schemas"]["luminary_node.core.model.LuminaryStatus"]; 3 + </script> 4 + 5 + <script lang="ts"> 6 + import { faCircleCheck, faCirclePause, faCircleXmark, faHeart } from "@fortawesome/free-regular-svg-icons"; 7 + import { faHourglassStart, faPowerOff } from "@fortawesome/free-solid-svg-icons"; 8 + import type { IconDefinition } from "@fortawesome/fontawesome-common-types"; 9 + import type { components } from "$lib/api/openapi"; 10 + import Fa from "svelte-fa"; 11 + 12 + const STATUS_MAP = { 13 + healthy: [faHeart, "green"], 14 + running: [faCircleCheck, "green"], 15 + paused: [faCirclePause, "peach"], 16 + exited: [faCircleXmark, "red"], 17 + down: [faPowerOff, "text"], 18 + loading: [faHourglassStart, "sky"], 19 + } satisfies { [key in LuminaryStatus]: [IconDefinition, string] }; 20 + 21 + let { status }: { status: LuminaryStatus } = $props(); 22 + let [icon, color] = $derived(STATUS_MAP[status]); 23 + </script> 24 + 25 + <span style="color: var(--{color})" class="container"> 26 + <Fa {icon} /> 27 + </span> 28 + 29 + <style lang="scss"> 30 + .container { 31 + display: inline-flex; 32 + align-items: center; 33 + justify-content: center; 34 + width: 1em; 35 + } 36 + </style>
+4 -1
packages/panel/src/routes/(authenticated)/+layout.svelte
··· 6 6 7 7 <div class="container"> 8 8 <Navbar /> 9 - {@render children()} 9 + 10 + <div class="full"> 11 + {@render children()} 12 + </div> 10 13 </div> 11 14 12 15 <style lang="scss">
+89 -1
packages/panel/src/routes/(authenticated)/projects/+page.svelte
··· 1 - <h1>Hello World</h1> 1 + <script lang="ts"> 2 + import StatusIcon, { type LuminaryStatus } from "$lib/component/StatusIcon.svelte"; 3 + import { Accordion } from "melt/builders"; 4 + import { getList } from "$lib/api"; 5 + import Fa from "svelte-fa"; 6 + import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons"; 7 + 8 + const ORDER = ["healthy", "running", "exited", "paused", "down", "paused"] as LuminaryStatus[]; 9 + 10 + const accordion = new Accordion({ multiple: true, value: ORDER }); 11 + 12 + let groups = $derived( 13 + Object.entries(Object.groupBy(Object.values(getList()), (project) => project.status)).toSorted( 14 + ([a], [b]) => ORDER.indexOf(a as LuminaryStatus) - ORDER.indexOf(b as LuminaryStatus), 15 + ), 16 + ); 17 + </script> 18 + 19 + <div class="flexc gap-10 full" {...accordion.root}> 20 + {#each groups as [status, projects]} 21 + {@const item = accordion.getItem({ id: status })} 22 + <button class="a divider" {...item.trigger} aria-label="toggle {status} projects"> 23 + <Fa icon={item.isExpanded ? faMinus : faPlus} /> 24 + <StatusIcon status={status as LuminaryStatus} /> 25 + <div {...item.heading}>{status} ({Object.keys(projects).length})</div> 26 + <hr /> 27 + </button> 28 + {#if item.isExpanded} 29 + <div class="grid" {...item.content}> 30 + {#each projects as project} 31 + <a href="/project/{project.name}" class="project"> 32 + <h2> 33 + <StatusIcon status={project.status} /> 34 + {project.name} 35 + </h2> 36 + <div style="color: var(--subtext0);"> 37 + {Object.keys(project.services).length} services {project.status} 38 + </div> 39 + </a> 40 + {/each} 41 + </div> 42 + {/if} 43 + {/each} 44 + </div> 45 + 46 + <style lang="scss"> 47 + .divider { 48 + display: flex; 49 + align-items: center; 50 + gap: 10px; 51 + 52 + color: var(--text); 53 + text-decoration: none !important; 54 + 55 + hr { 56 + border-color: var(--subtext0); 57 + flex-grow: 1; 58 + } 59 + } 60 + 61 + .grid { 62 + display: grid; 63 + grid-template-columns: repeat(auto-fit, minmax(300px, calc(100% / 3 - 20px))); 64 + width: 100%; 65 + gap: 10px; 66 + } 67 + 68 + .project { 69 + transition: background-color 250ms ease; 70 + background-color: var(--surface0); 71 + border-radius: 10px; 72 + padding: 10px; 73 + 74 + color: inherit; 75 + 76 + &:hover { 77 + background-color: var(--surface1); 78 + text-decoration: none; 79 + } 80 + 81 + h2 { 82 + display: flex; 83 + align-items: center; 84 + gap: 10px; 85 + 86 + margin-bottom: 5px; 87 + } 88 + } 89 + </style>