source, static files & Dockerfile
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
PROXMOX_URL=
|
||||
PROXMOX_TOKEN=
|
||||
HOMEASSISTANT_URL=
|
||||
HOMEASSISTANT_TOKEN=
|
||||
TRAEFIK_URL=
|
||||
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM node:22-alpine3.20 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY src/ src
|
||||
COPY static/ static
|
||||
COPY package.json yarn.lock svelte.config.js tsconfig.json vite.config.ts .
|
||||
|
||||
RUN yarn
|
||||
RUN yarn build
|
||||
|
||||
FROM node:22-alpine3.20
|
||||
|
||||
WORKDIR /opt/infra-map
|
||||
COPY --from=builder /app/build build
|
||||
|
||||
COPY package.json .
|
||||
RUN yarn
|
||||
|
||||
EXPOSE 3000
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD [ "node", "build" ]
|
||||
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# sv
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
13
src/app.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
10
src/hooks.server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
if (dev) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
}
|
||||
|
||||
return await resolve(event);
|
||||
};
|
||||
49
src/lib/components/Daemon.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import Pod from './Pod.svelte';
|
||||
import type { V1DaemonSet } from '@kubernetes/client-node';
|
||||
|
||||
export let daemon: V1DaemonSet;
|
||||
|
||||
let { metadata, status, pods } = daemon;
|
||||
|
||||
const healthy =
|
||||
status?.desiredNumberScheduled && status?.desiredNumberScheduled === status?.numberReady;
|
||||
</script>
|
||||
|
||||
<div class="card-container">
|
||||
<div class="namespace">
|
||||
<h2>{pods?.length} of {metadata?.name} in {metadata?.namespace}</h2>
|
||||
</div>
|
||||
|
||||
<p>heatlthy: {healthy}</p>
|
||||
|
||||
<div class="card-wrapper">
|
||||
{#each daemon?.pods as pod, i (pod)}
|
||||
<Pod parent={daemon} {pod} {i} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-container {
|
||||
background-color: #cab2aa40;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
|
||||
.namespace {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #077c35;
|
||||
}
|
||||
</style>
|
||||
41
src/lib/components/Deploy.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import Pod from './Pod.svelte';
|
||||
|
||||
import type { V1Deployment } from '@kubernetes/client-node';
|
||||
|
||||
export let deploy: V1Deployment;
|
||||
|
||||
let { metadata, pods } = deploy;
|
||||
</script>
|
||||
|
||||
<div class="card-container">
|
||||
<div class="namespace">
|
||||
<h2>{metadata?.name} in {metadata?.namespace}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-wrapper">
|
||||
{#each pods as pod, i (pod)}
|
||||
<Pod parent={deploy} {pod} {i} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-container {
|
||||
background-color: #cab2aa40;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
|
||||
.namespace {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
115
src/lib/components/Header.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
// Create a derived store to extract breadcrumb data
|
||||
const breadcrumbs = derived(page, ($page) => {
|
||||
const segments = $page.url.pathname.split('/').filter(Boolean); // Remove empty segments
|
||||
|
||||
return segments.map((segment, index) => {
|
||||
let label = decodeURI(segment);
|
||||
|
||||
// if not uuid pattern, this is weird order of ops
|
||||
/*
|
||||
if (!segment.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
|
||||
label = label.replace(/-/g, ' ')
|
||||
}
|
||||
*/
|
||||
|
||||
return {
|
||||
label,
|
||||
path: '/' + segments.slice(0, index + 1).join('/')
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<!-- <img src="/logo.png" /> -->
|
||||
<h1>schleppe.cloud</h1>
|
||||
</div>
|
||||
|
||||
<div class="middle crumbs">
|
||||
<a href="/">Home</a>
|
||||
{#each $breadcrumbs as crumb (crumb.label)}
|
||||
<span class="seperator">/</span>
|
||||
<a href={crumb.path}>{crumb.label}</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<span>User profile</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
left: 0;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr auto;
|
||||
grid-template-areas: 'logoSection siteAndEnvironment profileAndHelp';
|
||||
align-items: center;
|
||||
background: #1c1819;
|
||||
padding: 0 1rem;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
margin: 1rem 0.5rem 0 0.5rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
z-index: 100;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1rem;
|
||||
top: -1rem;
|
||||
background-color: var(--bg);
|
||||
/* opacity: 0.6; */
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
padding: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
img {
|
||||
padding: 0rem 0;
|
||||
max-height: 2.5rem;
|
||||
}
|
||||
|
||||
a,
|
||||
span {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.left,
|
||||
.middle,
|
||||
.right {
|
||||
min-height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
margin-left: 0.6rem;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.seperator {
|
||||
color: #7d6665;
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/lib/components/Lifecycle.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
export let conditions: any;
|
||||
|
||||
const dict = {
|
||||
Initialized: 'Initialized',
|
||||
PodScheduled: 'Scheduled',
|
||||
Ready: 'Ready',
|
||||
ContainersReady: 'AllReady'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="lifecycle-container">
|
||||
{#each conditions as condition (condition)}
|
||||
<div class="step" title={`Date: ${condition.lastTransitionTime}`}>
|
||||
<span class="type">{dict[condition.type]}</span>
|
||||
<span class="status">{condition.status === 'True' ? 'yes' : 'no'}</span>
|
||||
</div>
|
||||
<span class="divider"></span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.lifecycle-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: calc(100% - 1.5rem);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.divider:not(:last-of-type) {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
top: 2rem;
|
||||
margin-top: auto;
|
||||
margin-bottom: 0.5rem;
|
||||
background-color: var(--positive);
|
||||
opacity: 0.6;
|
||||
/*background-color: #0A7C35;*/
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.type {
|
||||
margin-bottom: 0.3rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
background-color: var(--positive);
|
||||
opacity: 0.6;
|
||||
/*background-color: #0a7c35;*/
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.2rem;
|
||||
padding-bottom: 0.3rem;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
src/lib/components/Logs.svelte
Normal file
@@ -0,0 +1,150 @@
|
||||
<script lang="ts">
|
||||
const logs1 = `+ yarn build
|
||||
yarn run v1.22.22
|
||||
$ vite build
|
||||
▲ [WARNING] Cannot find base config file "./.svelte-kit/tsconfig.json" [tsconfig.json]
|
||||
|
||||
tsconfig.json:2:12:
|
||||
2 │ "extends": "./.svelte-kit/tsconfig.json",
|
||||
╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
vite v4.4.7 building SSR bundle for production...
|
||||
transforming...
|
||||
"swipe" is imported from external module "svelte-gestures" but never used in "src/lib/components/Modal.svelte".
|
||||
✓ 139 modules transformed.
|
||||
rendering chunks...
|
||||
|
||||
vite v4.4.7 building for production...
|
||||
transforming...
|
||||
✓ 136 modules transformed.
|
||||
rendering chunks...
|
||||
computing gzip size...
|
||||
.svelte-kit/output/client/_app/version.json 0.03 kB │ gzip: 0.05 kB
|
||||
.svelte-kit/output/client/vite-manifest.json 6.06 kB │ gzip: 0.82 kB
|
||||
.svelte-kit/output/client/_app/immutable/assets/2.797db896.css 0.56 kB │ gzip: 0.21 kB
|
||||
.svelte-kit/output/client/_app/immutable/assets/LowBattery.3e0e6805.css 1.34 kB │ gzip: 0.50 kB
|
||||
.svelte-kit/output/client/_app/immutable/assets/0.4ae3ad4f.css 1.40 kB │ gzip: 0.54 kB
|
||||
.svelte-kit/output/client/_app/immutable/assets/4.d8e69ad3.css 3.84 kB │ gzip: 0.99 kB
|
||||
.svelte-kit/output/client/_app/immutable/assets/Graph.833c02c6.css 6.60 kB │ gzip: 1.42 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/each.e59479a4.js 0.09 kB │ gzip: 0.10 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/store.9bf92701.js 0.19 kB │ gzip: 0.16 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/stores.458b7cfe.js 0.24 kB │ gzip: 0.17 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/5.f2fe2a9e.js 0.44 kB │ gzip: 0.32 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/1.03b18b61.js 0.84 kB │ gzip: 0.52 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/index.4b915d9e.js 0.93 kB │ gzip: 0.58 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/3.60efb886.js 1.89 kB │ gzip: 0.89 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/index.e50902ae.js 2.29 kB │ gzip: 1.20 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/singletons.69e9afd3.js 2.46 kB │ gzip: 1.26 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/hive.5645a370.js 4.68 kB │ gzip: 2.37 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/scheduler.d05fc3de.js 6.27 kB │ gzip: 2.52 kB
|
||||
.svelte-kit/output/client/_app/immutable/entry/app.566aae85.js 7.82 kB │ gzip: 2.59 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/WeightChanged.86ed6639.js 9.66 kB │ gzip: 3.58 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/4.9e87f003.js 12.31 kB │ gzip: 4.21 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/0.0875ba29.js 16.68 kB │ gzip: 6.97 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/LowBattery.ced8ef00.js 18.26 kB │ gzip: 5.97 kB
|
||||
.svelte-kit/output/client/_app/immutable/entry/start.cf058992.js 24.06 kB │ gzip: 9.55 kB
|
||||
.svelte-kit/output/client/_app/immutable/nodes/2.02031f29.js 24.52 kB │ gzip: 7.00 kB
|
||||
.svelte-kit/output/client/_app/immutable/chunks/Graph.ed26d13c.js 238.67 kB │ gzip: 81.35 kB
|
||||
✓ built in 4.13s
|
||||
.svelte-kit/output/server/vite-manifest.json 3.81 kB
|
||||
.svelte-kit/output/server/_app/immutable/assets/_page.797db896.css 0.56 kB
|
||||
.svelte-kit/output/server/_app/immutable/assets/LowBattery.3e0e6805.css 1.34 kB
|
||||
.svelte-kit/output/server/_app/immutable/assets/_layout.4ae3ad4f.css 1.40 kB
|
||||
.svelte-kit/output/server/_app/immutable/assets/_page.d8e69ad3.css 3.84 kB
|
||||
.svelte-kit/output/server/_app/immutable/assets/Graph.d70e5aa3.css 6.58 kB
|
||||
.svelte-kit/output/server/internal.js 0.19 kB
|
||||
.svelte-kit/output/server/chunks/store.js 0.19 kB
|
||||
.svelte-kit/output/server/entries/pages/settings/_page.svelte.js 0.26 kB
|
||||
.svelte-kit/output/server/entries/fallbacks/error.svelte.js 0.47 kB
|
||||
.svelte-kit/output/server/chunks/stores.js 0.52 kB
|
||||
.svelte-kit/output/server/entries/pages/alarms/_page.svelte.js 1.14 kB
|
||||
.svelte-kit/output/server/chunks/index.js 2.58 kB
|
||||
.svelte-kit/output/server/chunks/hive.js 4.23 kB
|
||||
.svelte-kit/output/server/chunks/ssr.js 5.07 kB
|
||||
.svelte-kit/output/server/chunks/internal.js 5.52 kB
|
||||
.svelte-kit/output/server/chunks/WeightChanged.js 8.14 kB
|
||||
.svelte-kit/output/server/entries/pages/dashboard/_page.svelte.js 10.25 kB
|
||||
.svelte-kit/output/server/entries/pages/_layout.svelte.js 12.09 kB
|
||||
.svelte-kit/output/server/chunks/LowBattery.js 15.20 kB
|
||||
.svelte-kit/output/server/entries/pages/_page.svelte.js 15.97 kB
|
||||
.svelte-kit/output/server/chunks/Graph.js 28.69 kB
|
||||
.svelte-kit/output/server/index.js 87.83 kB
|
||||
|
||||
Run npm run preview to preview your production build locally.
|
||||
|
||||
> Using @sveltejs/adapter-static
|
||||
Wrote site to "build"
|
||||
✔ done
|
||||
✓ built in 11.35s
|
||||
Done in 13.18s.`;
|
||||
|
||||
export let logs: string[] = logs1.split('\n');
|
||||
export let lineNumbers = true;
|
||||
export let stream = false;
|
||||
|
||||
let codeElement: HTMLElement;
|
||||
|
||||
$: {
|
||||
console.log(logs.length);
|
||||
if (codeElement) {
|
||||
codeElement.scroll({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="parent" style="margin-bottom: 2rem">
|
||||
<div class="header"><button>follow</button></div>
|
||||
<div class="code" bind:this={codeElement}>
|
||||
<code>
|
||||
{#each logs as log, i (log + i)}
|
||||
<pre>{#if lineNumbers}<span class="line-number">{i + 1}</span>{/if}{log}</pre>
|
||||
{/each}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.parent {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: var(--color);
|
||||
max-height: calc(90vh - 10rem);
|
||||
color: white;
|
||||
border-radius: var(--border-radius, 1rem);
|
||||
padding: 1rem;
|
||||
overflow: scroll;
|
||||
|
||||
code {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
scroll-margin: 1rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.line-number {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
|
||||
display: inline-block;
|
||||
min-width: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
text-align: right;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
201
src/lib/components/Node.svelte
Normal file
@@ -0,0 +1,201 @@
|
||||
<script lang="ts">
|
||||
import Connection from '$lib/icons/connection.svelte';
|
||||
import Network from '$lib/icons/network.svelte';
|
||||
import Layers from '$lib/icons/layers.svelte';
|
||||
import Clock from '$lib/icons/clock.svelte';
|
||||
import { convertKiToHumanReadable } from '$lib/utils/conversion';
|
||||
|
||||
export let node;
|
||||
|
||||
let { metadata, pods, status } = node;
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<div class="icon"><Connection /></div>
|
||||
<span class="name">{metadata.name}</span>
|
||||
|
||||
<!--
|
||||
<span class={`status ${node?.online === 1 ? 'ok' : 'error'}`}></span>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="resource">
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>Status</span>
|
||||
</div>
|
||||
<span>{status.phase}</span>
|
||||
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>IP address</span>
|
||||
</div>
|
||||
<span>{status.addresses[0].address}</span>
|
||||
|
||||
<div class="title">
|
||||
<Layers />
|
||||
<span>Pods</span>
|
||||
</div>
|
||||
<span>{pods.length}</span>
|
||||
|
||||
<div class="title">
|
||||
<Connection />
|
||||
<span>CPUs allocated</span>
|
||||
</div>
|
||||
<span>{status.capacity.cpu}</span>
|
||||
|
||||
<div class="title">
|
||||
<Clock />
|
||||
<span>Memory allocaed</span>
|
||||
</div>
|
||||
<span>{convertKiToHumanReadable(status.capacity.memory)}</span>
|
||||
<!--
|
||||
<span>{uptime}</span>
|
||||
|
||||
<Lifecycle {conditions} />
|
||||
-->
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="footer">
|
||||
{#each buttons as btn (btn)}
|
||||
<button on:click={() => console.log(node)}>
|
||||
<span>{btn}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card {
|
||||
flex-grow: 1;
|
||||
max-width: 550px;
|
||||
|
||||
background: #fbf6f4;
|
||||
box-shadow: var(
|
||||
--str-shadow-s,
|
||||
0px 0px 2px #22242714,
|
||||
0px 1px 4px #2224271f,
|
||||
0px 4px 8px #22242729
|
||||
);
|
||||
pointer-events: all;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
|
||||
.icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 50%;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
|
||||
&.ok {
|
||||
background-color: var(--positive);
|
||||
}
|
||||
&.warning {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
&.error {
|
||||
background-color: var(--negative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.resource {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bg);
|
||||
|
||||
row-gap: 6px;
|
||||
column-gap: 20px;
|
||||
|
||||
> div,
|
||||
span {
|
||||
display: flex;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.resource .title svg) {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
margin-top: auto;
|
||||
background: white;
|
||||
padding: 0.5rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
height: unset;
|
||||
border-radius: 0.5rem;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
padding: 0 0.5rem;
|
||||
flex: 1;
|
||||
|
||||
span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
margin-left: -0.5rem;
|
||||
border: 1px solid #eaddd5;
|
||||
border-radius: inherit;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.1s ease;
|
||||
will-change: box-shadow 0.25s;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #077c35;
|
||||
}
|
||||
</style>
|
||||
16
src/lib/components/PageHeader.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<h1><slot></slot></h1>
|
||||
|
||||
<style lang="scss">
|
||||
@font-face {
|
||||
font-family: 'Reckless Neue';
|
||||
font-style: normal;
|
||||
src: url('/fonts/RecklessNeue-Regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Reckless Neue';
|
||||
font-weight: 400;
|
||||
font-size: 2.3rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
203
src/lib/components/Pod.svelte
Normal file
@@ -0,0 +1,203 @@
|
||||
<script lang="ts">
|
||||
import Connection from '$lib/icons/connection.svelte';
|
||||
import Network from '$lib/icons/network.svelte';
|
||||
import Layers from '$lib/icons/layers.svelte';
|
||||
import Clock from '$lib/icons/clock.svelte';
|
||||
import { formatDuration } from '$lib/utils/conversion';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import type { V1DaemonSet, V1Deployment, V1Pod } from '@kubernetes/client-node';
|
||||
import { writable } from 'svelte/store';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let pod: V1Pod;
|
||||
export let parent: V1Deployment | V1DaemonSet;
|
||||
export let i: number;
|
||||
|
||||
let { metadata, spec, status } = pod;
|
||||
|
||||
// set name
|
||||
const name =
|
||||
metadata?.name || metadata?.labels?.app || metadata?.labels?.['app.kubernetes.io/app'];
|
||||
|
||||
// set replicas
|
||||
let replicas = -1;
|
||||
if (parent.spec?.['replicas'] !== undefined) {
|
||||
parent = parent as V1Deployment;
|
||||
replicas = parent.spec?.replicas || replicas;
|
||||
} else if (parent.status?.['currentNumberScheduled'] !== undefined) {
|
||||
parent = parent as V1DaemonSet;
|
||||
replicas = parent.status?.currentNumberScheduled || replicas;
|
||||
}
|
||||
|
||||
// set uptime
|
||||
let uptime = writable(new Date().getTime() - new Date(status?.startTime || 0).getTime());
|
||||
|
||||
onMount(() => {
|
||||
setInterval(() => uptime.update((n) => n + 1000), 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<div class="icon"><Layers /></div>
|
||||
<span class="name">{name}</span>
|
||||
|
||||
<!--
|
||||
<span class={`status ${node?.online === 1 ? 'ok' : 'error'}`}></span>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="resource">
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>Status</span>
|
||||
</div>
|
||||
<span>{status?.phase}</span>
|
||||
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>Pod IP address</span>
|
||||
</div>
|
||||
<span>{status?.podIP}</span>
|
||||
|
||||
<div class="title">
|
||||
<Layers />
|
||||
<span>Instances</span>
|
||||
</div>
|
||||
<span>{i + 1} of {replicas}</span>
|
||||
|
||||
<div class="title">
|
||||
<Connection />
|
||||
<span>Running on Node</span>
|
||||
</div>
|
||||
<span>{spec?.nodeName}</span>
|
||||
|
||||
<div class="title">
|
||||
<Clock />
|
||||
<span>Uptime</span>
|
||||
</div>
|
||||
<span>{formatDuration($uptime / 1000)}</span>
|
||||
<!--
|
||||
<span>{uptime}</span>
|
||||
|
||||
<Lifecycle {conditions} />
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<button on:click={() => goto(`/cluster/pod/${pod.metadata?.uid}`)}>
|
||||
<span>Pod details</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-container {
|
||||
background-color: #cab2aa40;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
|
||||
.namespace {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
flex-grow: 1;
|
||||
max-width: 550px;
|
||||
|
||||
background: #fbf6f4;
|
||||
box-shadow: var(
|
||||
--str-shadow-s,
|
||||
0px 0px 2px #22242714,
|
||||
0px 1px 4px #2224271f,
|
||||
0px 4px 8px #22242729
|
||||
);
|
||||
pointer-events: all;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
|
||||
.icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 50%;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
|
||||
&.ok {
|
||||
background-color: var(--positive);
|
||||
}
|
||||
&.warning {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
&.error {
|
||||
background-color: var(--negative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.resource {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bg);
|
||||
|
||||
row-gap: 6px;
|
||||
column-gap: 20px;
|
||||
|
||||
> div,
|
||||
span {
|
||||
display: flex;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.resource .title svg) {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
margin-top: auto;
|
||||
background: white;
|
||||
padding: 0.5rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #077c35;
|
||||
}
|
||||
</style>
|
||||
75
src/lib/components/Progress.svelte
Normal file
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
export let value: number | string;
|
||||
export let max: number = 100;
|
||||
</script>
|
||||
|
||||
<label for="file">Print progress</label>
|
||||
|
||||
<progress id="file" class="completed" {max} {value}>{value}%</progress>
|
||||
|
||||
<style lang="scss">
|
||||
label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
progress {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
border: 1px solid #ced7e6;
|
||||
opacity: 0.6;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
margin-top: 0.5rem;
|
||||
|
||||
&.completed::-webkit-progress-value {
|
||||
background: unset !important;
|
||||
animation: unset !important;
|
||||
background-size: unset !important;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgb(255 80 24 / 48%),
|
||||
rgb(255 80 24 / 92%)
|
||||
) !important;
|
||||
}
|
||||
|
||||
&,
|
||||
&::-webkit-progress-inner-element,
|
||||
&::-webkit-progress-value,
|
||||
&::-webkit-progress-bar {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
border: 1px solid #a92200;
|
||||
margin-left: -1px;
|
||||
height: calc(100% + 2px);
|
||||
transform: translateY(-1px);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
|
||||
--width: 12px;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(255, 80, 24),
|
||||
rgb(255, 80, 24) var(--width),
|
||||
rgb(255 80 24 / 48%) var(--width),
|
||||
rgb(255 80 24 / 48%) calc(var(--width) * 2)
|
||||
);
|
||||
|
||||
background-size: 200% 100%;
|
||||
animation: progress-animation 14s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-animation {
|
||||
0% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-progress-bar {
|
||||
background: #f9f5f3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/lib/components/Section.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
export let title: string;
|
||||
export let description: string;
|
||||
</script>
|
||||
|
||||
<article class="main-container">
|
||||
<div class="header">
|
||||
<h2>{title}</h2>
|
||||
<label>{description}</label>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</article>
|
||||
|
||||
<style lang="scss">
|
||||
article {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: unset;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
259
src/lib/components/Server.svelte
Normal file
@@ -0,0 +1,259 @@
|
||||
<script lang="ts">
|
||||
import ServerIcon from '$lib/icons/server.svelte';
|
||||
import CPU from '$lib/icons/cpu.svelte';
|
||||
import Shield from '$lib/icons/shield.svelte';
|
||||
import Floppy from '$lib/icons/floppy-disk.svelte';
|
||||
import Box from '$lib/icons/box.svelte';
|
||||
import LXC from '$lib/icons/cube-side.svelte';
|
||||
import Network from '$lib/icons/network.svelte';
|
||||
import Clock from '$lib/icons/clock.svelte';
|
||||
|
||||
import { formatBytes, formatDuration } from '$lib/utils/conversion';
|
||||
import type { Node } from '$lib/interfaces/proxmox';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const buttons = ['View logs', 'Web terminal', 'graphs'];
|
||||
|
||||
let { cpuinfo, memory, uptime, loadavg } = node.info;
|
||||
|
||||
const vmsRunning = node.vms.filter((v) => v?.template !== 1 && v.status === 'running');
|
||||
const vmsTotal = node.vms.filter((v) => v?.template !== 1);
|
||||
const lxcsRunning = node.lxcs.filter((l) => l?.template !== 1 && l.status === 'running');
|
||||
const lxcsTotal = node.lxcs.filter((l) => l?.template !== 1);
|
||||
|
||||
onMount(() => {
|
||||
setInterval(() => (uptime += 1), 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<div class="icon"><ServerIcon /></div>
|
||||
<span class="name">{node?.name}</span>
|
||||
|
||||
<span class={`status ${node?.online === 1 ? 'ok' : 'error'}`}></span>
|
||||
</div>
|
||||
|
||||
<div class="resource">
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>Load</span>
|
||||
</div>
|
||||
<span>{loadavg}</span>
|
||||
|
||||
<div class="title">
|
||||
<CPU />
|
||||
<span>CPU cores</span>
|
||||
</div>
|
||||
<span
|
||||
>{cpuinfo.cpus} Cores on {cpuinfo.sockets} {cpuinfo.sockets > 1 ? 'Sockets' : 'Socket'}</span
|
||||
>
|
||||
|
||||
<div class="title">
|
||||
<Shield />
|
||||
<span>DDoS protection</span>
|
||||
</div>
|
||||
<span class="positive">Enabled</span>
|
||||
|
||||
<div class="title">
|
||||
<Network />
|
||||
<span>IPs</span>
|
||||
</div>
|
||||
<span>{node?.ip}</span>
|
||||
|
||||
<div class="title">
|
||||
<Floppy />
|
||||
<span>Memory</span>
|
||||
</div>
|
||||
<span>{formatBytes(memory?.total)}</span>
|
||||
|
||||
<div class="title">
|
||||
<Box />
|
||||
<span>VMs</span>
|
||||
</div>
|
||||
<span>{vmsRunning.length} / {vmsTotal.length}</span>
|
||||
|
||||
<div class="title">
|
||||
<LXC />
|
||||
<span>LXCs</span>
|
||||
</div>
|
||||
<span>{lxcsRunning.length} / {lxcsTotal.length}</span>
|
||||
|
||||
<div class="title">
|
||||
<Clock />
|
||||
<span>Uptime</span>
|
||||
</div>
|
||||
<span>{formatDuration(uptime)}</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
{#each buttons as btn (btn)}
|
||||
<button on:click={() => console.log(node)}>
|
||||
<span>{btn}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes pulse-live {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 212, 57, 0.7);
|
||||
box-shadow: 0 0 0 0 rgba(0, 212, 57, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(0, 212, 57, 0);
|
||||
box-shadow: 0 0 0 10px rgba(0, 212, 57, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 212, 57, 0);
|
||||
box-shadow: 0 0 0 0 rgba(0, 212, 57, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin pulse-dot {
|
||||
&::after {
|
||||
content: '';
|
||||
top: 50%;
|
||||
margin-left: 0.4rem;
|
||||
position: absolute;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: pulse-live 2s infinite;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
background: #fbf6f4;
|
||||
box-shadow: var(
|
||||
--str-shadow-s,
|
||||
0px 0px 2px #22242714,
|
||||
0px 1px 4px #2224271f,
|
||||
0px 4px 8px #22242729
|
||||
);
|
||||
pointer-events: all;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
|
||||
.icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 50%;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
|
||||
&.ok {
|
||||
--color: var(--positive);
|
||||
@include pulse-dot;
|
||||
}
|
||||
&.warning {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
&.error {
|
||||
background-color: var(--negative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.resource {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bg);
|
||||
|
||||
row-gap: 6px;
|
||||
column-gap: 20px;
|
||||
|
||||
> div,
|
||||
span {
|
||||
display: flex;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.resource .title svg) {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
margin-top: auto;
|
||||
background: white;
|
||||
padding: 0.5rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
height: unset;
|
||||
border-radius: 0.5rem;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
padding: 0 0.5rem;
|
||||
flex: 1;
|
||||
|
||||
span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
margin-left: -0.5rem;
|
||||
border: 1px solid #eaddd5;
|
||||
border-radius: inherit;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.1s ease;
|
||||
will-change: box-shadow 0.25s;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #077c35;
|
||||
}
|
||||
</style>
|
||||
112
src/lib/components/Sidebar.svelte
Normal file
@@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
const pages = [
|
||||
{
|
||||
name: 'Home',
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
name: 'Sites',
|
||||
path: '/sites'
|
||||
},
|
||||
{
|
||||
name: 'Servers',
|
||||
path: '/servers'
|
||||
},
|
||||
{
|
||||
name: 'Printer',
|
||||
path: '/printer'
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
path: '/network'
|
||||
},
|
||||
{
|
||||
name: 'Cluster',
|
||||
path: '/cluster'
|
||||
},
|
||||
{
|
||||
name: 'Health',
|
||||
path: '/health'
|
||||
}
|
||||
];
|
||||
|
||||
const activePage = derived(page, ($page) => $page.url.pathname);
|
||||
</script>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<nav>
|
||||
{#each pages as page, i (page.name)}
|
||||
{#if i === 0}
|
||||
<a class={$activePage === page.path ? 'highlight' : ''} href={page.path}>{page.name}</a>
|
||||
{:else}
|
||||
<a
|
||||
class={`${$activePage !== page.path && $activePage.startsWith(page.path) ? 'child' : ''} ${$activePage.startsWith(page.path) ? 'highlight' : ''}`}
|
||||
href={page.path}>{page.name}</a
|
||||
>
|
||||
{/if}
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.nav-wrapper {
|
||||
--nav-width: 240px;
|
||||
top: 72px;
|
||||
left: 0;
|
||||
min-width: var(--nav-width);
|
||||
margin-right: 1rem;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
--nav-width: 100px;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
width: var(--nav-width);
|
||||
gap: 4px;
|
||||
margin-top: 1rem;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 600;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 0.5rem;
|
||||
transition: 0.3s ease all;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover,
|
||||
&.highlight {
|
||||
background-color: var(--highlight);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
transition: inherit;
|
||||
left: -2rem;
|
||||
opacity: 0;
|
||||
content: '->';
|
||||
}
|
||||
|
||||
&.child {
|
||||
padding-left: 2.5rem;
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
content: '->';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
src/lib/components/Table.svelte
Normal file
@@ -0,0 +1,136 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let title = '';
|
||||
export let description = '';
|
||||
export let columns: Array<string> | object;
|
||||
export let data: Array<unknown> = [];
|
||||
export let links: Array<string> = [];
|
||||
export let footer = '';
|
||||
|
||||
const hasLinks = links?.length > 0;
|
||||
let displayColumns: string[] = [];
|
||||
if (typeof columns === 'object' && !Array.isArray(columns)) {
|
||||
displayColumns = Object.values(columns);
|
||||
columns = Object.keys(columns);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="main-container">
|
||||
<div class="header">
|
||||
<h2>{title}</h2>
|
||||
<div class="description">{description}</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{#if displayColumns.length > 0}
|
||||
{#each displayColumns as column (column)}
|
||||
<th>{column}</th>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each columns as column (column)}
|
||||
<th>{column}</th>
|
||||
{/each}
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data as row, i (row)}
|
||||
<tr on:click={() => hasLinks && goto(links[i])} class={hasLinks ? 'link' : ''}>
|
||||
{#each columns as column (column)}
|
||||
{#if column === 'Link'}
|
||||
<td><a href={row[column]}>Link</a></td>
|
||||
{:else if column === 'Hex'}
|
||||
<td><span class="color" style={`background: ${row[column]}`} /></td>
|
||||
{:else if Array.isArray(row[column])}
|
||||
<td>{row[column].join(', ')}</td>
|
||||
{:else}
|
||||
<td>{row[column]}</td>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if footer?.length}
|
||||
<footer>{footer}</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: sans-serif;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 500;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
font-stretch: 2px;
|
||||
border-bottom: 1px solid #eaddd5;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:not(&:last-of-type) {
|
||||
border-bottom: 1px solid #eaddd5;
|
||||
}
|
||||
|
||||
&:hover > td {
|
||||
background-color: var(--highlight);
|
||||
background-color: #f5ede9;
|
||||
}
|
||||
|
||||
&.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.color {
|
||||
--size: 2rem;
|
||||
display: block;
|
||||
width: calc(var(--size) * 2);
|
||||
height: var(--size);
|
||||
margin-top: -calc(var(--size / 2));
|
||||
margin-bottom: -calc(var(--size / 2));
|
||||
border-radius: var(--border-radius, 1rem);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
49
src/lib/components/navigation/Tab.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
import { TABS } from './Tabs.svelte';
|
||||
|
||||
const tab = {};
|
||||
const { registerTab, selectTab, selectedTab } = getContext(TABS);
|
||||
|
||||
registerTab(tab);
|
||||
</script>
|
||||
|
||||
<div class="tab">
|
||||
<button class:selected={$selectedTab === tab} on:click={() => selectTab(tab)}>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
letter-spacing: 0.2px;
|
||||
|
||||
&.selected {
|
||||
opacity: 1;
|
||||
letter-spacing: unset;
|
||||
border-bottom-color: var(--color) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
&:not(&:first-of-type) {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
opacity: 0.7;
|
||||
padding-bottom: 0.3rem;
|
||||
transition: 0.3s ease-in-out all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
src/lib/components/navigation/TabList.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="tab-list">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.tab-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
29
src/lib/components/navigation/TabView.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { fly, scale, slide, crossfade } from 'svelte/transition';
|
||||
import { TABS } from './Tabs.svelte';
|
||||
|
||||
const panel = {
|
||||
id: Math.random() * 100
|
||||
};
|
||||
const { registerPanel, selectedPanel } = getContext(TABS);
|
||||
|
||||
registerPanel(panel);
|
||||
|
||||
const [send, receive] = crossfade({ duration: 800 });
|
||||
</script>
|
||||
|
||||
{#if $selectedPanel === panel}
|
||||
<div in:fly={{ x: 200, duration: 500 }} out:fly={{ x: -50, duration: 300 }}>
|
||||
<!-- <div in:send="{{key: panel}}" out:receive="{{key: panel}}"> -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
position: absolute;
|
||||
top: calc(3.25rem);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
63
src/lib/components/navigation/Tabs.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<script context="module">
|
||||
export const TABS = {};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { setContext, onDestroy } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const tabs = [];
|
||||
const panels = [];
|
||||
const selectedTab = writable(null);
|
||||
const selectedPanel = writable(null);
|
||||
|
||||
setContext(TABS, {
|
||||
registerTab: (tab) => {
|
||||
tabs.push(tab);
|
||||
selectedTab.update((current) => current || tab);
|
||||
|
||||
onDestroy(() => {
|
||||
const i = tabs.indexOf(tab);
|
||||
tabs.splice(i, 1);
|
||||
selectedTab.update((current) =>
|
||||
current === tab ? tabs[i] || tabs[tabs.length - 1] : current
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
registerPanel: (panel) => {
|
||||
panels.push(panel);
|
||||
selectedPanel.update((current) => current || panel);
|
||||
|
||||
onDestroy(() => {
|
||||
const i = panels.indexOf(panel);
|
||||
panels.splice(i, 1);
|
||||
selectedPanel.update((current) =>
|
||||
current === panel ? panels[i] || panels[panels.length - 1] : current
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
selectTab: (tab) => {
|
||||
const i = tabs.indexOf(tab);
|
||||
selectedTab.set(tab);
|
||||
selectedPanel.set(panels[i]);
|
||||
},
|
||||
|
||||
selectedTab,
|
||||
selectedPanel
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="tabs">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.tabs {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
98
src/lib/icons/Logo.svelte
Normal file
@@ -0,0 +1,98 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 677.000000 429.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,429.000000) scale(0.100000,-0.100000)"
|
||||
fill="white"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M2980 4190 c-455 -39 -874 -260 -1157 -610 -110 -136 -225 -332 -285
|
||||
-487 -15 -40 -19 -43 -55 -43 -21 0 -53 -4 -71 -9 -18 -6 -59 -13 -90 -16
|
||||
-160 -16 -265 -46 -417 -120 -251 -121 -432 -297 -617 -597 -56 -91 -127 -301
|
||||
-153 -453 -23 -136 -30 -335 -15 -466 11 -102 19 -146 51 -271 28 -113 119
|
||||
-298 209 -426 70 -100 227 -256 326 -325 160 -111 388 -209 550 -236 38 -6 85
|
||||
-15 104 -18 58 -12 3985 -15 4185 -4 218 12 308 32 449 101 234 114 400 282
|
||||
511 515 70 146 96 259 103 441 7 212 -22 352 -113 534 -83 166 -195 293 -360
|
||||
406 -71 49 -81 61 -131 148 -147 261 -387 498 -629 621 -176 90 -328 132 -505
|
||||
142 l-106 6 -18 46 c-94 234 -234 449 -405 621 -232 234 -461 368 -771 449
|
||||
-146 38 -207 47 -385 55 -60 2 -153 1 -205 -4z m389 -245 c353 -53 712 -265
|
||||
927 -547 26 -35 51 -65 54 -68 3 -3 14 -21 23 -40 10 -19 28 -53 40 -75 31
|
||||
-55 127 -269 127 -282 0 -6 4 -13 8 -15 4 -1 14 -20 22 -41 8 -21 26 -44 40
|
||||
-52 15 -8 91 -18 185 -24 195 -13 293 -38 449 -114 159 -78 310 -200 430 -347
|
||||
43 -53 129 -194 167 -274 25 -53 46 -86 56 -86 20 0 88 -37 138 -75 21 -16 42
|
||||
-30 46 -30 24 0 192 -206 216 -265 23 -57 47 -105 56 -108 4 -2 5 -7 3 -11 -4
|
||||
-7 -1 -24 21 -121 4 -19 8 -100 8 -180 -1 -165 -16 -231 -87 -377 -64 -132
|
||||
-249 -328 -363 -385 -56 -28 -189 -74 -250 -85 -22 -5 -996 -8 -2165 -8 -1711
|
||||
0 -2138 3 -2190 13 -385 78 -704 312 -867 637 -147 292 -169 686 -57 1021 111
|
||||
333 353 601 654 727 133 56 195 68 365 73 242 7 238 6 268 62 14 26 33 70 42
|
||||
97 27 78 111 240 179 345 220 336 552 555 956 630 122 23 364 25 499 5z"
|
||||
/>
|
||||
<path
|
||||
d="M2324 3061 c-95 -23 -177 -91 -218 -180 -19 -42 -21 -66 -24 -273 -3
|
||||
-173 0 -233 10 -252 17 -32 207 -226 222 -226 33 0 36 23 36 305 1 205 4 284
|
||||
13 295 35 44 41 45 293 50 l244 5 2 -150 3 -150 80 -5 80 -5 3 -237 2 -238 26
|
||||
0 c30 0 110 39 154 76 51 44 60 77 60 226 0 184 -3 178 80 178 67 0 68 0 79
|
||||
31 6 18 11 85 11 150 l0 119 231 0 c132 0 238 -4 249 -10 10 -5 28 -25 39 -44
|
||||
21 -33 21 -40 21 -770 0 -782 0 -782 -46 -800 -9 -3 -325 -6 -703 -6 -377 0
|
||||
-692 0 -698 0 -7 0 -13 -6 -13 -14 0 -8 -33 -91 -72 -185 -49 -113 -79 -171
|
||||
-90 -175 -10 -2 -18 -11 -18 -20 0 -14 66 -16 703 -16 l703 0 29 -62 30 -63
|
||||
189 -3 c183 -2 190 -2 213 20 l23 21 0 1088 0 1088 -24 55 c-29 66 -85 124
|
||||
-151 156 l-50 25 -845 2 c-465 1 -859 -2 -876 -6z"
|
||||
/>
|
||||
<path
|
||||
d="M1335 2303 c-218 -20 -408 -131 -589 -348 -125 -148 -189 -343 -187
|
||||
-565 1 -176 55 -356 142 -473 73 -98 135 -121 189 -67 32 32 26 81 -16 136
|
||||
-58 75 -101 171 -122 272 l-20 92 54 0 54 0 0 60 0 60 -32 2 c-18 1 -43 1 -56
|
||||
0 -21 -1 -22 2 -16 36 24 144 108 315 192 396 l31 28 31 -35 c31 -36 51 -41
|
||||
73 -17 9 10 9 11 0 6 -7 -4 -13 -3 -13 3 0 5 13 15 29 22 l29 13 -30 39 c-29
|
||||
36 -30 39 -12 52 47 36 160 87 230 105 104 27 114 26 115 -7 0 -16 6 -44 12
|
||||
-63 10 -29 17 -35 42 -36 42 0 52 10 66 72 12 53 13 54 48 54 100 0 270 -72
|
||||
388 -164 124 -97 226 -262 257 -418 22 -107 25 -101 -44 -93 l-60 7 0 -61 0
|
||||
-60 59 -3 58 -3 -13 -55 c-24 -92 -54 -160 -121 -267 -35 -55 -63 -109 -63
|
||||
-120 0 -40 14 -62 47 -78 30 -15 42 -15 75 -6 48 15 113 79 129 129 6 20 25
|
||||
63 41 95 73 147 102 347 73 507 -9 52 -17 102 -17 110 0 8 -24 65 -53 125 -55
|
||||
113 -149 240 -236 320 -63 58 -187 133 -266 161 -101 36 -324 53 -498 37z"
|
||||
/>
|
||||
<path
|
||||
d="M5148 2168 c-83 -10 -139 -45 -159 -96 -5 -13 -27 -31 -51 -41 -82
|
||||
-36 -84 -36 -102 9 -9 22 -19 40 -22 40 -4 0 -32 -21 -63 -46 -64 -52 -89 -99
|
||||
-90 -166 -1 -34 -8 -54 -26 -76 -14 -17 -25 -35 -25 -41 0 -17 -17 -13 -61 15
|
||||
-23 14 -42 24 -43 22 -8 -10 -36 -129 -36 -152 0 -32 20 -74 48 -99 17 -15 19
|
||||
-24 11 -50 -5 -18 -9 -47 -9 -66 0 -34 0 -34 -40 -28 -22 3 -42 2 -44 -1 -6
|
||||
-11 40 -160 58 -187 9 -14 31 -33 49 -41 23 -11 38 -30 52 -62 11 -25 21 -49
|
||||
23 -52 1 -4 -13 -15 -33 -26 -19 -10 -35 -24 -35 -30 0 -15 63 -72 112 -102
|
||||
23 -14 61 -26 95 -29 71 -7 93 -24 93 -72 0 -61 10 -79 47 -92 51 -17 141 -3
|
||||
177 28 23 19 37 23 59 19 16 -3 40 -6 53 -6 22 0 24 -4 24 -45 0 -51 -2 -50
|
||||
87 -31 75 16 108 36 134 81 25 42 128 91 122 58 -2 -7 1 -13 7 -13 5 0 12 -16
|
||||
16 -35 3 -19 9 -35 12 -35 11 0 92 72 113 102 33 45 49 98 38 126 -9 24 5 69
|
||||
34 107 14 18 17 18 34 4 10 -8 31 -23 46 -33 l28 -17 15 37 c9 21 20 64 25 96
|
||||
10 64 -2 104 -41 131 -18 12 -19 21 -14 74 l7 60 47 -5 48 -4 -5 43 c-13 116
|
||||
-58 200 -115 214 -13 3 -34 24 -47 47 l-24 42 38 34 37 34 -34 35 c-49 51
|
||||
-120 93 -157 93 -45 0 -81 26 -81 60 0 70 -10 88 -58 104 -63 22 -117 20 -167
|
||||
-5 -34 -17 -53 -20 -89 -15 -44 7 -46 9 -46 39 0 49 -8 54 -72 45z m168 -560
|
||||
c64 -44 104 -111 111 -188 5 -51 2 -65 -20 -103 -29 -49 -96 -113 -133 -127
|
||||
-53 -21 -133 -12 -194 21 -52 27 -62 38 -90 96 -29 58 -32 71 -26 121 7 60 28
|
||||
102 73 143 56 53 94 69 164 69 59 0 73 -4 115 -32z"
|
||||
/>
|
||||
<path
|
||||
d="M1777 1831 c-53 -6 -61 -10 -103 -56 -25 -28 -64 -75 -86 -105 -53
|
||||
-72 -60 -77 -121 -85 -68 -9 -97 -25 -141 -76 -71 -83 -70 -191 2 -271 87 -96
|
||||
204 -103 298 -16 55 50 68 91 60 180 -6 67 -5 72 34 145 22 42 40 81 40 88 0
|
||||
6 9 21 20 33 11 12 20 28 20 37 0 10 5 13 12 9 7 -4 8 -3 4 5 -9 15 20 62 36
|
||||
58 17 -3 22 31 8 48 -7 8 -15 14 -19 14 -3 -1 -32 -5 -64 -8z"
|
||||
/>
|
||||
<path
|
||||
d="M2943 1818 c-42 -14 -68 -63 -59 -110 4 -24 18 -45 46 -67 22 -18 40
|
||||
-38 40 -46 0 -13 -24 -69 -98 -225 -22 -47 -43 -95 -46 -107 l-7 -23 375 0
|
||||
c353 0 375 1 369 18 -4 9 -22 49 -41 87 -67 137 -122 267 -116 276 3 5 14 9
|
||||
24 9 10 0 32 12 47 26 15 14 22 22 14 18 -11 -6 -12 -3 -6 12 19 41 10 86 -23
|
||||
116 l-32 28 -228 -1 c-125 -1 -241 -6 -259 -11z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
18
src/lib/icons/Thermometer.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M352 480v-384c0-52.9 43.1-96 96-96s96 43.1 96 96v384c16.9 12.7 31.2 28.6 41.9 46.9 14.5 24.4 22.1 52.5 22.1 81.1 0 88.2-71.8 160-160 160s-160-71.8-160-160c0-50.6 24.1-98.1 64-128zM448 704c52.9 0 96-43.1 96-96 0-34.2-18.4-66-48-83.2-9.9-5.7-16-16.3-16-27.7v-401.1c0-17.6-14.4-32-32-32s-32 14.4-32 32v401.1c0 11.4-6.1 22-16 27.7-29.6 17.1-48 49-48 83.2 0 52.9 43.1 96 96 96z"
|
||||
></path>
|
||||
<path
|
||||
d="M447.998 544v32c17.6 0 32 14.4 32 32s-14.4 32-32 32v32c35.3 0 64-28.7 64-64s-28.7-64-64-64z"
|
||||
></path>
|
||||
<path
|
||||
d="M288 416h-64v32h64v-32zM288 352h-64v32h64v-32zM288 288h-128v32h128v-32zM288 224h-64v32h64v-32zM288 160h-64v32h64v-32zM288 96h-128v32h128v-32z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 823 B |
11
src/lib/icons/box.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<path
|
||||
d="M701.2 186.6c0 0 0 0 0 0l-288-147.6c-18.3-9.3-40.1-9.3-58.4 0l-288 147.6c-21.4 11-34.8 32.9-34.8 57v280.9c0 24.1 13.3 45.9 34.8 57l288 147.6c9.1 4.7 19.1 7 29.1 7s20.1-2.3 29.2-7l288-147.6c21.4-11 34.8-32.9 34.8-57v-280.9c0.1-24.1-13.2-45.9-34.7-57zM384 352l-107.3-55 249.8-128 107.3 55-249.8 128zM384 96l107.4 55-249.8 128-107.4-55 249.8-128zM96 276.4l256 131.2v248.2l-256-131.3v-248.1zM416 655.7v-248.1l256-131.2v248.2l-256 131.1z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
13
src/lib/icons/branches.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M704 517.5v-101.5c0-35.3-28.7-64-64-64h-224v-101.5c37.2-13.2 64-48.8 64-90.5 0-52.9-43.1-96-96-96s-96 43.1-96 96c0 41.7 26.8 77.3 64 90.5v101.5h-224c-35.3 0-64 28.7-64 64v101.5c-37.2 13.2-64 48.8-64 90.5 0 52.9 43.1 96 96 96s96-43.1 96-96c0-41.7-26.8-77.3-64-90.5v-101.5h224v101.5c-37.2 13.2-64 48.8-64 90.5 0 52.9 43.1 96 96 96s96-43.1 96-96c0-41.7-26.8-77.3-64-90.5v-101.5h224v101.5c-37.2 13.2-64 48.8-64 90.5 0 52.9 43.1 96 96 96s96-43.1 96-96c0-41.7-26.8-77.3-64-90.5zM320 160c0-35.3 28.7-64 64-64s64 28.7 64 64-28.7 64-64 64c-35.3 0-64-28.7-64-64zM160 608c0 35.3-28.7 64-64 64s-64-28.7-64-64 28.7-64 64-64 64 28.7 64 64zM448 608c0 35.3-28.7 64-64 64s-64-28.7-64-64 28.7-64 64-64c35.3 0 64 28.7 64 64zM672 672c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 980 B |
18
src/lib/icons/clock.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M737.8 234.5c-19.3-45.7-47-86.8-82.3-122-35.3-35.3-76.3-62.9-122-82.3-47.4-20-97.7-30.2-149.5-30.2s-102.1 10.2-149.5 30.2c-45.7 19.3-86.8 47-122 82.3-35.3 35.3-62.9 76.3-82.3 122-20 47.4-30.2 97.7-30.2 149.5s10.2 102.1 30.2 149.5c19.3 45.7 47 86.8 82.3 122 35.3 35.3 76.3 62.9 122 82.3 47.4 20 97.7 30.2 149.5 30.2s102.1-10.2 149.5-30.2c45.7-19.3 86.8-47 122-82.3 35.3-35.3 62.9-76.3 82.3-122 20-47.4 30.2-97.7 30.2-149.5s-10.2-102.1-30.2-149.5zM384 704c-176.4 0-320-143.6-320-320s143.6-320 320-320c176.4 0 320 143.6 320 320s-143.6 320-320 320z"
|
||||
></path>
|
||||
<path
|
||||
d="M528 249.4l-135.7 135.7c-2.6-0.7-5.4-1.1-8.3-1.1-4.7 0-9.2 1-13.2 2.9l-102.5-73.2-18.6 26 102.5 73.2c-0.1 1-0.1 2-0.1 3.1 0 17.6 14.4 32 32 32s32-14.4 32-32c0-2.9-0.4-5.6-1.1-8.3l135.6-135.7-22.6-22.6z"
|
||||
></path>
|
||||
<path
|
||||
d="M384 96c-76.9 0-149.3 30-203.6 84.4s-84.4 126.7-84.4 203.6 30 149.3 84.4 203.6c54.3 54.4 126.7 84.4 203.6 84.4s149.3-30 203.6-84.4c54.4-54.3 84.4-126.7 84.4-203.6s-30-149.3-84.4-203.6c-54.3-54.4-126.7-84.4-203.6-84.4zM384 640c-141.2 0-256-114.8-256-256s114.8-256 256-256c141.2 0 256 114.8 256 256s-114.8 256-256 256z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
12
src/lib/icons/connection.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M704 384h-128v-240c0-8.8-7.2-16-16-16h-144v-64c0-35.3-28.7-64-64-64h-288c-35.3 0-64 28.7-64 64v160c0 35.3 28.7 64 64 64h42.2l-9.8 58.7c-1.5 9.3 1.1 18.8 7.1 25.9s15 11.3 24.4 11.3h160c9.4 0 18.3-4.1 24.4-11.3s8.7-16.7 7.1-25.9l-9.8-58.7h42.4c35.3 0 64-28.7 64-64v-64h128v224h-128c-35.3 0-64 28.7-64 64v160c0 35.3 28.7 64 64 64h42.2l-9.8 58.7c-1.5 9.3 1.1 18.8 7.1 25.9 6.1 7.2 15 11.3 24.4 11.3h160c9.4 0 18.3-4.1 24.4-11.3s8.7-16.7 7.1-25.9l-9.8-58.7h42.4c35.3 0 64-28.7 64-64v-160c0-35.3-28.7-64-64-64zM255.6 352h-95.1l10.7-64h73.8l10.6 64zM352 224h-288v-160h288v160c0 0 0 0 0 0zM607.6 736h-95.1l10.7-64h73.8l10.6 64zM704 608h-288v-160h288v160c0 0 0 0 0 0z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 829 B |
25
src/lib/icons/cpu-x86.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M768 256v-32h-96v-32c0-52.9-43.1-96-96-96h-32v-96h-32v96h-64v-96h-32v96h-64v-96h-32v96h-64v-96h-32v96h-32c-52.9 0-96 43.1-96 96v32h-96v32h96v64h-96v32h96v64h-96v32h96v64h-96v32h96v32c0 52.9 43.1 96 96 96h32v96h32v-96h64v96h32v-96h64v96h32v-96h64v96h32v-96h32c52.9 0 96-43.1 96-96v-32h96v-32h-96v-64h96v-32h-96v-64h96v-32h-96v-64h96zM608 576c0 17.6-14.4 32-32 32h-384c-17.6 0-32-14.4-32-32v-384c0-17.6 14.4-32 32-32h384c17.6 0 32 14.4 32 32v384z"
|
||||
></path>
|
||||
<path
|
||||
d="M512 192c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zM512 288c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32z"
|
||||
></path>
|
||||
<path
|
||||
d="M272 441.4l-32 32-32-32-22.6 22.6 32 32-32 32 22.6 22.6 32-32 32 32 22.6-22.6-32-32 32-32z"
|
||||
></path>
|
||||
<path
|
||||
d="M475.1 449.9c-17.5 21-27.1 48.7-27.1 78.1 0 26.5 21.5 48 48 48s48-21.5 48-48-21.5-48-48-48c-1.1 0-2.2 0-3.2 0.1 11.7-19.5 30.3-32.1 51.2-32.1v-32c-26.2 0-50.7 12-68.9 33.9zM496 512c8.8 0 16 7.2 16 16s-7.2 16-16 16-16-7.2-16-16 7.2-16 16-16z"
|
||||
></path>
|
||||
<path
|
||||
d="M368 416c-26.5 0-48 21.5-48 48 0 12.3 4.6 23.5 12.3 32-7.6 8.5-12.3 19.7-12.3 32 0 26.5 21.5 48 48 48s48-21.5 48-48c0-12.3-4.6-23.5-12.3-32 7.6-8.5 12.3-19.7 12.3-32 0-26.5-21.5-48-48-48zM368 448c8.8 0 16 7.2 16 16s-7.2 16-16 16-16-7.2-16-16 7.2-16 16-16zM368 544c-8.8 0-16-7.2-16-16s7.2-16 16-16 16 7.2 16 16-7.2 16-16 16z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
16
src/lib/icons/cpu.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M768 256v-32h-96v-32c0-52.9-43.1-96-96-96h-32v-96h-32v96h-64v-96h-32v96h-64v-96h-32v96h-64v-96h-32v96h-32c-52.9 0-96 43.1-96 96v32h-96v32h96v64h-96v32h96v64h-96v32h96v64h-96v32h96v32c0 52.9 43.1 96 96 96h32v96h32v-96h64v96h32v-96h64v96h32v-96h64v96h32v-96h32c52.9 0 96-43.1 96-96v-32h96v-32h-96v-64h96v-32h-96v-64h96v-32h-96v-64h96zM608 576c0 17.6-14.4 32-32 32h-384c-17.6 0-32-14.4-32-32v-384c0-17.6 14.4-32 32-32h384c17.6 0 32 14.4 32 32v384z"
|
||||
></path>
|
||||
<path
|
||||
d="M512 192c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zM512 288c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 816 B |
16
src/lib/icons/cube-side.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M701.2 186.6c0 0 0 0 0 0l-288-147.6c-18.3-9.3-40.1-9.3-58.4 0l-288 147.6c-21.4 11-34.8 32.9-34.8 57v280.9c0 24.1 13.3 45.9 34.8 57l288 147.6c9.1 4.7 19.1 7 29.1 7s20.1-2.3 29.2-7l288-147.6c21.4-11 34.8-32.9 34.8-57v-280.9c0.1-24.1-13.2-45.9-34.7-57zM384 96l249.8 128-249.8 128-249.8-128 249.8-128zM96 276.4l256 131.2v248.2l-256-131.3v-248.1zM416 655.7v-248.1l256-131.2v248.2l-256 131.1z"
|
||||
></path>
|
||||
<path
|
||||
d="M632.3 341.2c-4.8-2.9-10.7-3.1-15.6-0.6l-160 82c-5.3 2.7-8.7 8.2-8.7 14.2v140.2c0 5.6 2.9 10.7 7.7 13.7 2.6 1.6 5.4 2.3 8.3 2.3 2.5 0 5-0.6 7.3-1.8l160-82c5.3-2.7 8.7-8.2 8.7-14.2v-140.1c0-5.6-2.9-10.8-7.7-13.7zM608 485.3l-128 65.6v-104.2l128-65.6v104.2z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 866 B |
13
src/lib/icons/database.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M666.9 54.1c-17.3-11.3-41.1-21.3-70.7-29.5-57.1-15.9-132.5-24.6-212.2-24.6s-155.1 8.7-212.2 24.6c-29.6 8.2-53.4 18.2-70.7 29.5-30.7 20.1-37.1 42.6-37.1 57.9v544c0 15.3 6.4 37.8 37.1 57.9 17.3 11.3 41.1 21.3 70.7 29.5 57.1 15.9 132.5 24.6 212.2 24.6s155.1-8.7 212.2-24.6c29.7-8.2 53.4-18.2 70.7-29.5 30.6-20.1 37.1-42.6 37.1-57.9v-544c0-15.3-6.4-37.8-37.1-57.9zM640 402.1c-11.1 7.2-29 15.3-56.6 23-53.1 14.8-123.9 22.9-199.4 22.9s-146.3-8.1-199.4-22.8c-27.7-7.7-45.6-15.8-56.6-23v-91.5c13.4 6.4 29.4 12.2 48.1 17.4 55.7 15.5 129.6 24 207.9 24s152.2-8.5 207.9-24c18.6-5.2 34.7-11 48.1-17.4v91.4zM640 274.1c-11.1 7.2-29 15.3-56.6 23-53.1 14.8-123.9 22.9-199.4 22.9s-146.3-8.1-199.4-22.8c-27.7-7.7-45.6-15.8-56.6-23v-89.9c12.7 5.6 27.4 10.6 43.8 15.2 57.1 15.8 132.5 24.5 212.2 24.5s155.1-8.7 212.2-24.6c16.4-4.6 31.1-9.7 43.8-15.2v89.9zM128 438.6c13.4 6.4 29.4 12.2 48.1 17.4 55.7 15.5 129.6 24 207.9 24s152.2-8.5 207.9-24c18.6-5.2 34.7-11 48.1-17.4v91.5c-11.1 7.2-29 15.3-56.6 23-53.1 14.8-123.9 22.9-199.4 22.9s-146.3-8.1-199.4-22.8c-27.7-7.7-45.6-15.8-56.6-23v-91.6zM203.1 82.6c49.9-12 114.1-18.6 180.9-18.6s131 6.6 180.9 18.6c45.2 10.9 65.7 22.9 72.9 29.4-7.2 6.5-27.7 18.5-72.9 29.4-49.9 12-114.1 18.6-180.9 18.6s-131-6.6-180.9-18.6c-45.2-10.9-65.7-22.9-72.9-29.4 7.2-6.5 27.7-18.5 72.9-29.4zM564.9 685.4c-49.9 12-114.1 18.6-180.9 18.6s-131-6.6-180.9-18.6c-51.3-12.3-70.8-26.2-75.1-31.7v-87.1c13.4 6.4 29.4 12.2 48.1 17.4 55.7 15.5 129.6 24 207.9 24s152.2-8.5 207.9-24c18.6-5.2 34.7-11 48.1-17.4v87.1c-4.3 5.5-23.8 19.4-75.1 31.7z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
13
src/lib/icons/earth.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M737.8 234.5c-19.3-45.7-47-86.8-82.3-122-35.3-35.3-76.3-62.9-122-82.3-47.4-20-97.7-30.2-149.5-30.2s-102.1 10.2-149.5 30.2c-45.7 19.3-86.8 47-122 82.3-35.3 35.3-62.9 76.3-82.3 122-20 47.4-30.2 97.7-30.2 149.5s10.2 102.1 30.2 149.5c19.3 45.7 47 86.8 82.3 122 35.3 35.3 76.3 62.9 122 82.3 47.4 20 97.7 30.2 149.5 30.2s102.1-10.2 149.5-30.2c45.7-19.3 86.8-47 122-82.3 35.3-35.3 62.9-76.3 82.3-122 20-47.4 30.2-97.7 30.2-149.5s-10.2-102.1-30.2-149.5zM658.5 219.6c-4.1 2-8.2 4.6-12.2 7.7l-2.1 1.6-1.5 2.2c-15 22.2-38.5 29-54.1 24.2-8.4-2.6-12.7-7.7-12.7-15.3 0-21.6-11.5-38.4-20.7-52-8-11.8-12.8-19.3-11.2-24.8 1.4-4.8 8.6-14.7 39.1-29.6 30 23.9 55.6 53.1 75.4 86zM384 64c6.5 0 12.9 0.2 19.2 0.6-3.2 2.1-7 3.8-11.3 5.8-12.5 5.7-29.7 13.6-38.7 35.6l-0.3 0.6-0.2 0.6c-20.6 65.2-48.2 90.8-66.5 107.7-13.1 12.1-24.4 22.6-24.6 40-0.2 15.1 7.9 31.1 29.8 59 17.5 22.3 34.3 33.6 51.4 34.8 21.6 1.4 36.8-13.4 49.1-25.3 6.2-6 12.6-12.3 17.4-13.9 1.3-0.4 4.2-1.4 11.5 5.8 26.6 26.6 48.3 33.6 64.2 38.8 14.7 4.8 21.4 7 28.9 21.3 12.5 23.7 31.4 32.9 45.2 39.6 15.1 7.3 17 9.2 17 17 0 5.1 0.2 10.6 0.3 16.3 0.6 20.4 1.4 51.3-7.8 60.8-1.3 1.4-3.5 2.9-8.5 2.9-31.9 0-44.7 28.4-53.2 47.2-2.4 5.4-6.4 14.1-9 16.9-20.9-2.9-46.5 21-86.2 59.8-8.8 8.6-20.4 20-29.6 28 1-10.6 3.4-26.5 8.6-49.1 7-30.7 17-63.8 24.2-80.5 4.7-10.9 6-27.9-14.6-46.6-11.1-10-26.4-19-41.3-27.7-10.7-6.2-20.7-12.1-28.4-17.9-8.6-6.5-10.2-9.8-10.4-10.4 0-5.9 1.1-12.6 2.1-19.7 4.1-26.7 10.2-67.1-43.7-90.7-5.6-2.5-11.5-4.8-17.1-7-45.1-17.8-91.6-36.2-99.9-160 57.4-55.8 135.9-90.3 222.4-90.3zM91.6 513.9c19.1 3.3 33-10.7 43.3-21 3.4-3.4 10.5-10.5 13.4-11.2 1.4 0.6 11.5 6 28.9 52 10.2 27 10.7 54.8 11.2 86.9 0.1 5.4 0.2 10.9 0.3 16.7-41.8-32.3-75.4-74.7-97.1-123.4zM221.5 659.6c-0.8-13.7-1-26.8-1.3-39.6-0.6-33.7-1.1-65.4-13.3-97.7-17.8-47-32.9-67.8-52.2-72-18.7-4-32.5 9.9-42.6 20-4.1 4.1-12.5 12.5-15.2 12-1.1-0.2-10.7-3.3-27.9-41.8-3.3-18.4-5-37.2-5-56.5 0-74.5 25.6-143.2 68.5-197.6 5.8 43 17.3 76.2 34.7 100.7 24.1 34 55.1 46.2 82.4 57 5.6 2.2 10.9 4.3 16.1 6.6 31.4 13.7 29 29.8 24.9 56.5-1.2 8.1-2.5 16.5-2.5 24.8 0 23.7 26.6 39.2 54.7 55.7 13.4 7.9 27.3 16 35.9 23.8 7.4 6.7 7 9.5 6.7 10.2-8.4 19.5-19.6 56.8-27.1 90.8-4.1 18.4-6.8 34.8-8.1 47.5-1.8 18.7-0.4 29.8 4.6 37.1 1.8 2.6 4.2 4.7 6.9 6.2-50.9-3.6-98.6-19.1-140.2-43.7zM384 704c-1.6 0-3.1 0-4.7 0 12.6-4.5 28-19.2 54.6-45.2 12.8-12.5 26-25.4 37.7-35.4 14.7-12.5 20.9-15 22.6-15.4 6.9 1 18.9 0.3 29.1-12.6 5.2-6.5 8.8-14.5 12.6-23 9-20 14.4-28.3 24.1-28.3 12.5 0 23.4-4.4 31.4-12.6 18.7-19.2 17.7-53.6 16.9-84-0.2-5.5-0.3-10.7-0.3-15.4 0-28.9-20.3-38.7-35.1-45.8-11.9-5.8-23.2-11.2-30.8-25.6-13.6-25.9-30.8-31.5-47.4-36.9-14.2-4.6-30.3-9.9-51.5-31-14.1-14.1-29-18.7-44.3-13.5-11.7 3.9-20.7 12.7-29.5 21.3-8.9 8.6-17.2 16.8-24.6 16.3-4.2-0.3-13.4-3.6-28.4-22.6-15.3-19.5-23.1-32.5-23-38.7 0.1-3.6 5.5-8.8 14.4-17 19.4-18 51.9-48.1 75.1-120.8 4-9.4 10.5-12.7 22.2-18.1 12-5.5 27-12.4 35.6-30.4 40.9 7.4 79.1 22.5 113 43.8-23.7 13.5-36.2 26.6-40.5 41.5-5.9 20.3 5.5 37 15.5 51.7 7.8 11.4 15.2 22.2 15.2 33.9 0 21.6 13.5 39.2 35.3 45.9 5.9 1.8 12.5 2.8 19.4 2.8 23 0 49.9-10.7 69-37.3 2.1-1.5 4.1-2.6 5.9-3.5 19.6 41.1 30.5 87.2 30.5 135.9 0 176.4-143.6 320-320 320z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
28
src/lib/icons/finished.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="100%"
|
||||
width="100%"
|
||||
viewBox="0 0 115.000000 108.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,108.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M504 1040 c-117 -17 -195 -56 -279 -139 -152 -151 -188 -386 -87
|
||||
-575 36 -68 135 -167 200 -200 142 -72 306 -74 448 -6 59 29 153 116 194 180
|
||||
97 151 97 368 1 519 -101 157 -297 247 -477 221z m249 -113 c88 -43 152 -108
|
||||
193 -195 37 -75 45 -212 19 -293 -37 -116 -120 -209 -233 -262 -61 -29 -75
|
||||
-32 -162 -31 -74 0 -107 5 -150 22 -276 111 -356 452 -157 669 89 96 184 135
|
||||
323 130 83 -3 99 -7 167 -40z"
|
||||
/>
|
||||
<path
|
||||
d="M629 604 l-126 -126 -49 51 c-27 28 -56 51 -64 51 -19 0 -40 -19 -40
|
||||
-37 0 -15 140 -153 155 -153 6 0 77 67 159 149 135 135 147 150 137 170 -7 11
|
||||
-19 21 -29 21 -9 0 -74 -57 -143 -126z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 896 B |
16
src/lib/icons/floppy-disk.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M672 32h-576c-35.3 0-64 28.7-64 64v498.7c0 17.1 6.7 33.1 18.8 45.2l77.3 77.3c12.1 12.1 28.2 18.8 45.2 18.8h498.7c35.3 0 64-28.7 64-64v-576c0-35.3-28.7-64-64-64zM192 64h384v256c0 17.6-14.4 32-32 32h-320c-17.6 0-32-14.4-32-32v-256zM544 704h-288v-192h288v192zM672 672h-96v-176c0-8.8-7.2-16-16-16h-320c-8.8 0-16 7.2-16 16v176h-50.7l-77.3-77.3v-498.7h64v224c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64v-224h64v576z"
|
||||
></path>
|
||||
<path
|
||||
d="M304 672h64c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16h-64c-8.8 0-16 7.2-16 16v96c0 8.8 7.2 16 16 16zM320 576h32v64h-32v-64z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 760 B |
23
src/lib/icons/hard-disk.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M640 0h-512c-35.3 0-64 28.7-64 64v640c0 35.3 28.7 64 64 64h512c35.3 0 64-28.7 64-64v-640c0-35.3-28.7-64-64-64zM640 704h-512v-640h512v640c0 0 0 0 0 0z"
|
||||
></path>
|
||||
<path
|
||||
d="M224 544c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zM224 640c-17.6 0-32-14.4-32-32s14.4-32 32-32 32 14.4 32 32-14.4 32-32 32z"
|
||||
></path>
|
||||
<path d="M576 640h-192v32h208c8.8 0 16-7.2 16-16v-48h-32v32z"></path>
|
||||
<path
|
||||
d="M368 352c26.5 0 48-21.5 48-48s-21.5-48-48-48c-26.5 0-48 21.5-48 48s21.5 48 48 48zM368 288c8.8 0 16 7.2 16 16s-7.2 16-16 16-16-7.2-16-16 7.2-16 16-16z"
|
||||
></path>
|
||||
<path
|
||||
d="M563.8 451.1l-37.5-12.2c31.8-37.4 49.7-85.3 49.7-134.9 0-114.7-93.3-208-208-208s-208 93.3-208 208 93.3 208 208 208c18.5 0 36.8-2.4 54.5-7.2l84.7 59.5c5.2 3.6 10.9 6.5 17 8.5 6.6 2.1 13.2 3.2 19.8 3.2 27 0 52.1-17.2 60.9-44.2 5.3-16.3 3.9-33.6-3.8-48.8-7.8-15.3-21-26.6-37.3-31.9zM368 480c-97 0-176-79-176-176s79-176 176-176 176 79 176 176c0 46.5-18.6 91.2-51.2 124.1l-133.3-43.3h-0.5c-14.1-3.2-29.1 3.7-35.7 16.9-7 14.1-2.6 31.5 10.2 40.5l52.4 36.9c-5.8 0.6-11.9 0.9-17.9 0.9zM574.4 521.9c-5.5 16.8-23.6 26-40.3 20.5-2.8-0.9-5.4-2.2-7.8-3.7l-174.3-122.7 2.4 0.8 199.5 64.8c8.1 2.6 14.7 8.3 18.6 15.9s4.6 16.3 1.9 24.4z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
13
src/lib/icons/layers.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M768 400c0-6.3-3.8-12.1-9.6-14.7l-116.8-51.3 107.6-48.9c11.4-5.2 18.8-16.6 18.8-29.1s-7.3-23.9-18.8-29.1l-352-160c-8.4-3.8-18.1-3.8-26.5 0l-352 160c-11.4 5.2-18.7 16.6-18.7 29.1s7.3 23.9 18.8 29.1l107.6 48.9-116.8 51.3c-5.8 2.6-9.6 8.4-9.6 14.7s3.8 12.1 9.6 14.7l112.4 49.3-112.4 49.3c-5.8 2.6-9.6 8.4-9.6 14.7s3.8 12.1 9.6 14.7l368 161.6c2 0.9 4.2 1.3 6.4 1.3s4.4-0.5 6.4-1.3l368-161.6c5.8-2.6 9.6-8.3 9.6-14.7 0-6.3-3.8-12.1-9.6-14.7l-112.4-49.3 112.4-49.3c5.8-2.6 9.6-8.4 9.6-14.7zM384 131.2l274.7 124.8-274.7 124.8-274.7-124.8 274.7-124.8zM712.2 528l-328.2 144.1-328.2-144.1 106-46.5 215.8 94.8c2 0.9 4.2 1.3 6.4 1.3s4.4-0.5 6.4-1.3l215.8-94.8 106 46.5zM384 544.1l-328.2-144.1 109.7-48.2 205.3 93.3c4.2 1.9 8.7 2.9 13.2 2.9s9-1 13.2-2.9l205.3-93.3 109.7 48.2-328.2 144.1z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 979 B |
16
src/lib/icons/link.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M433 281.4l-34 54.3c6.7 4.2 12.9 9.1 18.5 14.7 40.5 40.5 40.5 106.5 0 147l-112.1 112.2c-20.3 20.3-46.9 30.4-73.5 30.4s-53.2-10.1-73.5-30.4c-40.5-40.5-40.5-106.5 0-147l64.2-63.9-45.2-45.3-64.2 64c-65.5 65.5-65.5 172 0 237.5 32.7 32.7 75.7 49.1 118.8 49.1 43 0 86-16.4 118.8-49.1l112.1-112.1c65.5-65.5 65.5-172 0-237.5-9.1-9.2-19.2-17.1-29.9-23.9z"
|
||||
></path>
|
||||
<path
|
||||
d="M654.8 113.2c-65.5-65.5-172-65.5-237.5 0l-112.1 112.1c-65.5 65.5-65.5 172 0 237.5 9 9 19.1 17 29.9 23.8l33.9-54.3c-6.7-4.2-12.9-9.1-18.5-14.7-40.5-40.5-40.5-106.5 0-147l112.1-112.1c40.5-40.5 106.5-40.5 147 0s40.5 106.5 0 147l-64.2 63.9 45.2 45.3 64.2-64c65.5-65.5 65.5-172 0-237.5z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 852 B |
16
src/lib/icons/map-marker.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M565 75c-48.3-48.4-112.6-75-181-75s-132.7 26.6-181 75c-48.4 48.4-75 112.6-75 181 0 85.4 39.1 195.8 116.4 328.1 56.4 96.7 112 168.5 114.4 171.5 6.1 7.8 15.4 12.4 25.3 12.4s19.2-4.6 25.3-12.4c2.3-3 58-74.8 114.4-171.5 77.1-132.3 116.2-242.7 116.2-328.1 0-68.4-26.6-132.7-75-181zM384 682c-57.3-80.4-192-284.5-192-426 0-105.9 86.1-192 192-192s192 86.1 192 192c0 141.5-134.7 345.6-192 426z"
|
||||
></path>
|
||||
<path
|
||||
d="M384 128c-70.6 0-128 57.4-128 128s57.4 128 128 128c70.6 0 128-57.4 128-128s-57.4-128-128-128zM384 352c-52.9 0-96-43.1-96-96s43.1-96 96-96 96 43.1 96 96-43.1 96-96 96z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 776 B |
18
src/lib/icons/microsd.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M607.9 0h-351.9c-35.3 0-64 28.7-64 64v146.8l-77.3 77.3c-12.1 12.1-18.8 28.2-18.8 45.2v50.7c0 8.5 3.4 16.6 9.4 22.6s14.1 9.4 22.6 9.4h32.1v18.7l-54.7 54.6c-6 6-9.4 14.1-9.4 22.6v192c0 35.3 28.7 64 64 64h448c35.3 0 64-28.7 64-64v-639.9c0-35.3-28.7-64-64-64zM607.9 704h-448v-178.7l54.7-54.6c6-6 9.4-14.1 9.4-22.6v-64c0-8.5-3.4-16.6-9.4-22.6s-14.1-9.4-22.6-9.4h-32.1v-18.7l77.3-77.3c11.9-11.9 18.8-28.5 18.8-45.3v-146.8h351.9v640z"
|
||||
></path>
|
||||
<path d="M288 128h32v96h-32v-96z"></path>
|
||||
<path d="M352 96h32v128h-32v-128z"></path>
|
||||
<path d="M416 128h32v96h-32v-96z"></path>
|
||||
<path d="M480 96h32v128h-32v-128z"></path>
|
||||
<path d="M544 128h32v96h-32v-96z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 845 B |
16
src/lib/icons/network.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M190.6 430.6l-45.3-45.3-136 136c-12.5 12.5-12.5 32.8 0 45.3l136 136 45.3-45.3-81.3-81.3h466.7v-64h-466.7l81.3-81.4z"
|
||||
></path>
|
||||
<path
|
||||
d="M758.6 201.4l-136-136-45.3 45.3 81.4 81.4h-466.7v64h466.7l-81.4 81.4 45.3 45.3 136-136c12.5-12.7 12.5-32.9 0-45.4z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
30
src/lib/icons/paused.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 128.000000 137.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,137.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M483 1145 c-323 -87 -481 -440 -333 -742 61 -123 180 -224 313 -265
|
||||
57 -17 86 -20 175 -16 122 6 164 19 264 85 214 140 289 422 175 663 -38 82
|
||||
-149 195 -227 233 -116 56 -256 72 -367 42z m219 -66 c245 -51 407 -309 343
|
||||
-548 -35 -134 -128 -245 -251 -303 -92 -43 -219 -50 -314 -18 -196 66 -311
|
||||
224 -312 427 -1 289 254 501 534 442z"
|
||||
/>
|
||||
<path
|
||||
d="M472 838 c-17 -17 -17 -369 0 -386 15 -15 61 -15 76 0 17 17 17 369
|
||||
0 386 -15 15 -61 15 -76 0z"
|
||||
/>
|
||||
<path
|
||||
d="M672 838 c-17 -17 -17 -369 0 -386 15 -15 61 -15 76 0 17 17 17 369
|
||||
0 386 -15 15 -61 15 -76 0z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 854 B |
13
src/lib/icons/power.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M669.3 307.2c-5.1-11.6-16.6-19.2-29.3-19.2h-179.6l82-245.9c4.8-14.3-1.1-30-14.1-37.7s-29.6-5.2-39.8 5.8l-384 416c-8.6 9.3-10.9 22.9-5.8 34.5s16.6 19.2 29.3 19.2h179.6l-82 245.9c-4.8 14.3 1.1 30 14.1 37.7 5.1 3 10.7 4.5 16.3 4.4 8.7 0 17.3-3.5 23.5-10.3l384-416c8.6-9.2 10.9-22.8 5.8-34.4zM333.4 605l48.9-146.8c3.3-9.8 1.6-20.5-4.4-28.8s-15.7-13.3-26-13.3h-150.8l233.5-253-48.9 146.8c-3.3 9.8-1.6 20.5 4.4 28.8s15.7 13.3 26 13.3h150.9l-233.6 253z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 647 B |
31
src/lib/icons/printer-idle.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 170.000000 151.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,151.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M149 1451 l-29 -29 0 -521 c0 -500 -1 -521 -18 -521 -43 0 -52 -29
|
||||
-52 -163 0 -101 3 -129 16 -141 14 -14 94 -16 733 -16 544 0 720 3 729 12 8 8
|
||||
12 56 12 150 0 127 -1 138 -20 148 -11 6 -28 10 -38 8 -16 -3 -17 31 -22 525
|
||||
l-5 529 -28 24 -28 24 -610 0 -611 0 -29 -29z m1249 -143 l3 -98 -206 0 -205
|
||||
0 0 30 0 30 -200 0 -200 0 0 -30 0 -30 -200 0 -200 0 0 100 0 100 603 -2 602
|
||||
-3 3 -97z m-478 -173 l0 -55 -130 0 -130 0 0 55 0 55 130 0 130 0 0 -55z
|
||||
m-330 -65 l0 -70 85 0 85 0 0 -53 c0 -48 12 -77 31 -77 22 0 39 35 39 81 l0
|
||||
49 80 0 80 0 0 70 0 70 205 0 205 0 0 -380 0 -380 -605 0 -605 0 0 380 0 380
|
||||
200 0 200 0 0 -70z m890 -850 l0 -90 -680 0 -680 0 0 90 0 90 680 0 680 0 0
|
||||
-90z"
|
||||
/>
|
||||
<path
|
||||
d="M367 543 c-13 -13 -7 -51 10 -57 9 -4 197 -6 419 -6 324 1 406 4 421
|
||||
15 14 11 16 18 7 32 -10 17 -38 18 -431 21 -231 1 -423 -1 -426 -5z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
36
src/lib/icons/printer-paused.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="139.000000pt"
|
||||
height="157.000000pt"
|
||||
viewBox="0 0 139.000000 157.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,157.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M173 1506 l-28 -24 -5 -529 -5 -528 -22 3 c-41 6 -53 -30 -53 -165 0
|
||||
-97 3 -125 16 -137 14 -14 82 -16 602 -16 352 0 593 4 602 10 12 7 16 37 18
|
||||
137 4 141 -4 166 -52 168 l-27 2 1 501 c0 540 -1 550 -51 586 -21 14 -75 16
|
||||
-496 16 l-472 0 -28 -24z m975 -148 l3 -98 -156 0 -155 0 0 30 0 30 -160 0
|
||||
-160 0 0 -30 0 -30 -160 0 -160 0 0 93 c0 52 3 97 7 100 3 4 216 6 472 5 l466
|
||||
-3 3 -97z m-378 -173 l0 -55 -90 0 -90 0 0 55 0 55 90 0 90 0 0 -55z m-250
|
||||
-65 l0 -70 160 0 160 0 0 70 0 70 155 0 155 0 -2 -380 -3 -380 -470 0 -470 0
|
||||
-3 380 -2 380 160 0 160 0 0 -70z m710 -850 l0 -90 -550 0 -550 0 0 90 0 90
|
||||
550 0 550 0 0 -90z"
|
||||
/>
|
||||
<path
|
||||
d="M452 884 c-21 -15 -22 -20 -22 -199 0 -179 1 -184 22 -199 27 -20
|
||||
158 -22 176 -4 9 9 12 68 12 205 0 180 -1 193 -19 203 -31 16 -143 12 -169 -6z
|
||||
m118 -199 l0 -145 -35 0 -35 0 0 145 0 145 35 0 35 0 0 -145z"
|
||||
/>
|
||||
<path
|
||||
d="M732 884 c-21 -15 -22 -20 -22 -199 0 -216 0 -215 105 -215 106 0
|
||||
105 -2 105 221 l0 189 -26 10 c-40 16 -136 12 -162 -6z m118 -199 l0 -145 -35
|
||||
0 -35 0 0 145 0 145 35 0 35 0 0 -145z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
30
src/lib/icons/printer-printing.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 163.000000 151.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,151.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M149 1461 l-29 -29 0 -519 0 -519 -32 -9 -33 -10 -3 -139 c-2 -93 1
|
||||
-143 9 -152 17 -21 1441 -21 1458 0 8 9 11 59 9 152 l-3 139 -32 10 -33 9 0
|
||||
519 0 519 -29 29 -29 29 -612 0 -612 0 -29 -29z m1241 -141 l0 -100 -205 0
|
||||
-205 0 0 30 0 30 -195 0 -195 0 0 -30 0 -30 -205 0 -205 0 0 84 c0 46 3 91 6
|
||||
100 6 14 68 16 605 16 l599 0 0 -100z m-480 -175 l0 -55 -125 0 -125 0 0 55 0
|
||||
55 125 0 125 0 0 -55z m-320 -65 l0 -70 80 0 80 0 0 -49 c0 -47 -1 -49 -50
|
||||
-79 -81 -50 -99 -113 -50 -175 20 -25 53 -38 173 -68 26 -6 47 -14 47 -18 0
|
||||
-3 -23 -19 -51 -35 -28 -16 -59 -41 -68 -57 l-16 -29 -121 0 c-128 0 -159 -9
|
||||
-152 -46 3 -18 15 -19 328 -19 l325 0 3 27 c4 36 -13 40 -171 36 -70 -2 -127
|
||||
-1 -127 2 1 3 24 19 53 35 111 64 81 162 -57 180 -58 8 -110 27 -121 45 -9 15
|
||||
30 57 76 80 39 21 49 45 49 126 l0 44 80 0 80 0 0 70 0 70 208 0 207 0 0 -380
|
||||
0 -380 -607 0 -608 0 0 380 0 380 205 0 205 0 0 -70z m873 -852 l3 -88 -676 0
|
||||
-675 0 2 87 c1 49 2 89 3 91 0 1 302 1 670 0 l671 -3 2 -87z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
28
src/lib/icons/printer-stopped.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="176.000000pt"
|
||||
height="156.000000pt"
|
||||
viewBox="0 0 176.000000 156.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,156.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M223 1486 l-28 -24 -5 -528 c-5 -501 -6 -527 -23 -530 -47 -7 -47 -7
|
||||
-47 -152 0 -94 4 -142 12 -150 17 -17 1439 -17 1456 0 8 8 12 56 12 150 l0
|
||||
138 -26 10 c-14 6 -30 8 -35 4 -5 -3 -9 201 -9 511 0 361 -3 523 -11 538 -6
|
||||
12 -21 30 -33 39 -19 16 -68 18 -628 18 l-607 0 -28 -24z m1235 -148 l3 -98
|
||||
-206 0 -205 0 0 30 0 30 -195 0 -195 0 0 -30 0 -30 -205 0 -205 0 0 84 c0 46
|
||||
3 92 6 100 6 15 65 16 603 14 l596 -3 3 -97z m-483 -173 l0 -55 -122 0 -123 0
|
||||
0 55 0 55 123 0 122 0 0 -55z m-315 -65 l0 -70 195 0 195 0 0 70 0 70 205 0
|
||||
205 0 0 -382 0 -383 -172 2 -173 2 -5 213 -5 213 -250 0 -250 0 -5 -213 -5
|
||||
-213 -170 -1 -170 0 -3 381 -2 381 205 0 205 0 0 -70z m383 -511 l-2 -181
|
||||
-188 -1 -188 -2 -3 183 -2 182 192 0 193 0 -2 -181z m487 -339 l0 -90 -675 0
|
||||
-675 0 0 90 0 90 675 0 675 0 0 -90z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
27
src/lib/icons/printing.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 132.000000 120.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,120.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M510 1100 c-301 -46 -495 -338 -425 -639 42 -175 184 -322 364 -375
|
||||
32 -9 91 -16 141 -16 149 0 257 45 360 149 135 136 187 311 145 489 -61 259
|
||||
-320 432 -585 392z m275 -114 c225 -111 313 -364 206 -591 -112 -238 -405
|
||||
-326 -634 -192 -25 15 -67 51 -95 82 -220 241 -125 615 185 725 48 17 76 20
|
||||
160 17 94 -3 108 -7 178 -41z"
|
||||
/>
|
||||
<path
|
||||
d="M686 675 c-46 -35 -68 -45 -97 -45 -28 0 -42 -6 -53 -22 -34 -48 -7
|
||||
-108 48 -108 32 0 66 24 66 47 0 6 32 35 71 63 59 45 70 57 67 79 -5 41 -36
|
||||
36 -102 -14z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 793 B |
22
src/lib/icons/server.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M736 320v-128c0-15.1-5.3-29.1-14.1-40.1v0l-117.4-145.9c-3-3.8-7.6-6-12.5-6h-416c-4.8 0-9.4 2.2-12.5 6l-117.4 146c-8.8 10.9-14.1 24.9-14.1 40v128c0 19.1 8.4 36.3 21.7 48-13.3 11.7-21.7 28.9-21.7 48v64c0 19.1 8.4 36.3 21.7 48-13.3 11.7-21.7 28.9-21.7 48v128c0 35.3 28.7 64 64 64h576c35.3 0 64-28.7 64-64v-128c0-19.1-8.4-36.3-21.7-48 13.3-11.7 21.7-28.9 21.7-48v-64c0-19.1-8.4-36.3-21.7-48 13.3-11.7 21.7-28.9 21.7-48zM183.7 32h400.7l77.2 96h-555.2l77.3-96zM672 704h-576l-0.1-128c0 0 0 0 0.1 0v-32h576v160zM672.1 480c0 0-0.1 0 0 0l-0.1 32h-576v-32l-0.1-64c0 0 0 0 0.1 0v-32h576v32l0.1 64zM96 352v-160h576v160h-576z"
|
||||
></path>
|
||||
<path d="M544 256h96v32h-96v-32z"></path>
|
||||
<path d="M480 256h32v32h-32v-32z"></path>
|
||||
<path d="M544 416h96v32h-96v-32z"></path>
|
||||
<path d="M480 416h32v32h-32v-32z"></path>
|
||||
<path d="M128 576h32v32h-32v-32z"></path>
|
||||
<path d="M192 576h32v32h-32v-32z"></path>
|
||||
<path d="M256 576h32v32h-32v-32z"></path>
|
||||
<path d="M320 576h32v32h-32v-32z"></path>
|
||||
<path d="M544 640h96v32h-96v-32z"></path>
|
||||
<path d="M480 640h32v32h-32v-32z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
16
src/lib/icons/shield.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="768"
|
||||
height="768"
|
||||
viewBox="0 0 768 768"
|
||||
>
|
||||
<g id="icomoon-ignore"> </g>
|
||||
<path
|
||||
d="M726.8 137.6c-6-6.1-14.2-9.6-22.8-9.6-42.5 0-101.4-15.4-165.8-43.4-54-23.4-106.3-53.6-133.3-76.8-12-10.3-29.8-10.3-41.8 0-27 23.3-79.3 53.5-133.3 76.9-64.4 27.9-123.3 43.3-165.8 43.3-8.6 0-16.8 3.5-22.8 9.6s-9.3 14.4-9.2 23c0.1 3.8 2.2 94.5 44.2 214 24.6 70 58 135.7 99.2 195.1 51.5 74.4 115.4 139.1 190 192.3 5.6 4 12.1 6 18.6 6s13-2 18.6-6c74.5-53.2 138.4-117.9 190-192.3 41.2-59.4 74.5-125.1 99.2-195.1 42-119.5 44.2-210.2 44.2-214 0.2-8.6-3.1-16.9-9.2-23zM630.6 355.6c-49.5 139.2-132.4 253.7-246.6 340.6-114.2-87-197.1-201.5-246.6-340.6-25.2-70.9-35.1-131.4-39-165.8 56-6.8 114.5-28.1 156.8-46.5 48.9-21.1 95.8-47 128.8-70.8 33 23.7 79.9 49.6 128.7 70.8 42.3 18.4 100.8 39.7 156.8 46.5-3.8 34.4-13.7 95-38.9 165.8z"
|
||||
></path>
|
||||
<path
|
||||
d="M374.7 131c-54.6 39-115.2 78.4-201.4 93.3-8 1.4-13.7 8.6-13.2 16.7 3.8 63.5 28.3 139.6 69.1 214.1 40 73.1 91.4 137.5 144.8 181.3 3 2.4 6.6 3.6 10.2 3.6s7.2-1.2 10.2-3.6c53.4-43.8 104.8-108.2 144.8-181.3 40.8-74.5 65.3-150.6 69.1-214.1 0.5-8.1-5.2-15.3-13.2-16.7-86.6-15-147.2-54.3-201.8-93.3-5.6-4-13-4-18.6 0zM384 603c-96.1-84.4-178.4-235.5-190.8-350 80.4-16.7 138.7-52.6 190.8-89.4 52.2 36.8 110.5 72.7 190.8 89.4-12.4 114.5-94.7 265.6-190.8 350z"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
26
src/lib/icons/stopped.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 141.000000 122.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,122.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M581 1094 c-132 -35 -266 -141 -329 -260 -135 -258 -33 -583 223
|
||||
-712 112 -56 268 -69 387 -32 258 80 419 366 353 625 -26 100 -63 165 -139
|
||||
241 -109 109 -222 155 -375 153 -36 0 -90 -7 -120 -15z m221 -65 c129 -27 255
|
||||
-125 311 -241 142 -296 -75 -648 -402 -648 -174 0 -329 98 -402 254 -157 336
|
||||
134 710 493 635z"
|
||||
/>
|
||||
<path
|
||||
d="M532 768 c-17 -17 -17 -339 0 -356 17 -17 339 -17 356 0 8 8 12 63
|
||||
12 180 0 155 -1 168 -19 178 -29 15 -334 13 -349 -2z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 746 B |
38
src/lib/icons/temperature-bed.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 152.000000 182.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,182.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M650 1704 c-45 -20 -77 -50 -101 -96 -17 -34 -19 -61 -19 -299 l0
|
||||
-262 -41 -44 c-60 -66 -83 -123 -83 -213 -1 -91 30 -167 94 -228 69 -68 120
|
||||
-87 230 -87 87 0 100 3 149 30 184 102 228 342 91 498 -33 37 -40 53 -40 86
|
||||
l0 40 53 3 c48 3 52 5 52 28 0 23 -4 25 -52 28 l-53 3 0 65 0 64 59 0 c44 0
|
||||
60 4 65 16 8 21 8 20 -10 38 -10 10 -33 16 -64 16 l-48 0 -4 104 c-3 99 -5
|
||||
106 -34 143 -59 74 -162 103 -244 67z m140 -69 c15 -8 38 -27 49 -41 20 -26
|
||||
21 -38 21 -303 l0 -277 36 -35 c171 -162 63 -439 -172 -439 -180 0 -308 198
|
||||
-228 353 10 17 37 56 61 85 l43 54 0 261 c0 287 2 296 59 334 39 26 89 30 131
|
||||
8z"
|
||||
/>
|
||||
<path
|
||||
d="M702 1458 c-9 -9 -12 -82 -12 -288 l0 -277 -26 -13 c-72 -39 -69
|
||||
-156 6 -195 49 -26 95 -19 137 18 32 30 36 40 36 81 0 37 -5 52 -28 76 -16 17
|
||||
-35 30 -42 30 -10 0 -13 60 -15 288 -3 279 -4 287 -23 290 -11 1 -26 -3 -33
|
||||
-10z"
|
||||
/>
|
||||
<path
|
||||
d="M204 397 c-2 -7 -3 -56 -2 -108 l3 -94 197 -3 c195 -2 197 -3 200
|
||||
-25 3 -21 7 -22 123 -22 117 0 120 1 123 22 3 22 5 23 210 25 l207 3 0 105 0
|
||||
105 -528 3 c-432 2 -528 0 -533 -11z m996 -92 l0 -45 -470 0 -470 0 0 38 c0
|
||||
21 3 42 7 45 3 4 215 7 470 7 l463 0 0 -45z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
31
src/lib/icons/temperature-nozzle.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 118.000000 181.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,181.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M475 1675 c-5 -2 -22 -6 -38 -9 -37 -9 -105 -71 -123 -114 -11 -28
|
||||
-14 -99 -14 -360 l0 -325 -41 -44 c-62 -68 -84 -123 -84 -218 0 -67 4 -89 28
|
||||
-136 31 -65 96 -129 161 -161 46 -22 55 -35 91 -133 20 -54 26 -60 51 -60 24
|
||||
0 30 5 40 42 22 74 64 139 100 152 54 20 121 87 156 158 58 118 39 255 -48
|
||||
352 l-44 49 0 105 0 106 46 3 c87 6 84 68 -2 68 l-44 0 0 60 0 59 56 3 c40 2
|
||||
60 8 67 20 19 29 -9 48 -69 48 l-54 0 0 78 c0 107 -14 149 -65 198 -48 46
|
||||
-126 74 -170 59z m107 -96 c56 -40 58 -57 58 -417 l0 -328 40 -39 c87 -85 108
|
||||
-205 54 -306 -88 -163 -308 -188 -427 -48 -37 43 -67 118 -67 169 0 51 32 121
|
||||
81 174 l48 53 3 347 3 348 37 34 c33 29 45 34 89 34 36 0 60 -6 81 -21z"
|
||||
/>
|
||||
<path
|
||||
d="M482 1408 c-9 -9 -12 -101 -12 -352 l-1 -341 -32 -20 c-94 -59 -47
|
||||
-215 64 -215 104 0 161 93 109 178 -11 18 -31 38 -45 44 l-25 11 0 342 c0 342
|
||||
-2 365 -35 365 -6 0 -16 -5 -23 -12z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
235
src/lib/interfaces/homeassistant.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
export interface Entity {
|
||||
entity_id: string;
|
||||
state: string;
|
||||
attributes: Attributes;
|
||||
last_changed: Date;
|
||||
last_reported: Date;
|
||||
last_updated: Date;
|
||||
context: Context;
|
||||
}
|
||||
|
||||
export interface Attributes {
|
||||
editable?: boolean;
|
||||
id?: string;
|
||||
device_trackers?: string[];
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
gps_accuracy?: number;
|
||||
source?: string;
|
||||
user_id?: string;
|
||||
friendly_name?: string;
|
||||
entity_id?: string[];
|
||||
icon?: string;
|
||||
min_color_temp_kelvin?: number;
|
||||
max_color_temp_kelvin?: number;
|
||||
min_mireds?: number;
|
||||
max_mireds?: number;
|
||||
supported_color_modes?: ColorMode[];
|
||||
color_mode?: ColorMode | null;
|
||||
brightness?: number | null;
|
||||
color_temp_kelvin?: number | null;
|
||||
color_temp?: number | null;
|
||||
hs_color?: number[] | null;
|
||||
rgb_color?: number[] | null;
|
||||
xy_color?: number[] | null;
|
||||
supported_features?: number;
|
||||
effect_list?: string[];
|
||||
effect?: null;
|
||||
initial?: null;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
mode?: Mode;
|
||||
unit_of_measurement?: string;
|
||||
radius?: number;
|
||||
passive?: boolean;
|
||||
persons?: string[];
|
||||
next_dawn?: Date;
|
||||
next_dusk?: Date;
|
||||
next_midnight?: Date;
|
||||
next_noon?: Date;
|
||||
next_rising?: Date;
|
||||
next_setting?: Date;
|
||||
elevation?: number;
|
||||
azimuth?: number;
|
||||
rising?: boolean;
|
||||
device_class?: string;
|
||||
has_date?: boolean;
|
||||
has_time?: boolean;
|
||||
hour?: number;
|
||||
minute?: number;
|
||||
second?: number;
|
||||
timestamp?: number;
|
||||
source_type?: string;
|
||||
'Fast User Switched'?: boolean;
|
||||
Idle?: boolean;
|
||||
Locked?: boolean;
|
||||
'Screen Off'?: boolean;
|
||||
Screensaver?: boolean;
|
||||
Sleeping?: boolean;
|
||||
Terminating?: boolean;
|
||||
'Battery Provides Time Remaining'?: boolean;
|
||||
BatteryHealth?: string;
|
||||
BatteryHealthCondition?: string;
|
||||
Current?: number;
|
||||
'Current Capacity'?: number;
|
||||
DesignCycleCount?: number;
|
||||
'Hardware Serial Number'?: string;
|
||||
'Is Charging'?: boolean;
|
||||
'Is Present'?: boolean;
|
||||
'LPM Active'?: boolean;
|
||||
'Max Capacity'?: number;
|
||||
Name?: string;
|
||||
'Optimized Battery Charging Engaged'?: boolean;
|
||||
'Power Source ID'?: number;
|
||||
'Power Source State'?: string;
|
||||
'Time to Empty'?: number;
|
||||
'Time to Full Charge'?: number;
|
||||
'Transport Type'?: string;
|
||||
Type?: string;
|
||||
'Low Power Mode'?: boolean;
|
||||
Available?: string;
|
||||
'Available (Important)'?: string;
|
||||
'Available (Opportunistic)'?: string;
|
||||
Total?: string;
|
||||
'Hardware Address'?: string;
|
||||
'Active Camera'?: any[];
|
||||
'All Camera'?: string[];
|
||||
'Active Audio Input'?: any[];
|
||||
'All Audio Input'?: string[];
|
||||
'Active Audio Output'?: any[];
|
||||
'All Audio Output'?: string[];
|
||||
'Display IDs'?: string[];
|
||||
'Display Names'?: string[];
|
||||
'Bundle Identifier'?: string;
|
||||
'Is Hidden'?: boolean;
|
||||
'Launch Date'?: Date;
|
||||
'Owns Menu Bar'?: boolean;
|
||||
'Allows VoIP'?: boolean;
|
||||
'Carrier ID'?: string;
|
||||
'Carrier Name'?: string;
|
||||
'ISO Country Code'?: string;
|
||||
'Mobile Country Code'?: string;
|
||||
'Mobile Network Code'?: string;
|
||||
battery_level?: number;
|
||||
altitude?: number;
|
||||
vertical_accuracy?: number;
|
||||
'Current Radio Technology'?: string;
|
||||
'Administrative Area'?: string;
|
||||
'Areas Of Interest'?: string;
|
||||
Country?: string;
|
||||
'Inland Water'?: string;
|
||||
Locality?: string;
|
||||
Location?: number[];
|
||||
Ocean?: string;
|
||||
'Postal Code'?: string;
|
||||
'Sub Administrative Area'?: string;
|
||||
'Sub Locality'?: string;
|
||||
'Sub Thoroughfare'?: string;
|
||||
Thoroughfare?: string;
|
||||
'Time Zone'?: string;
|
||||
Zones?: string[];
|
||||
temperature?: number | null;
|
||||
dew_point?: number;
|
||||
temperature_unit?: string;
|
||||
humidity?: number;
|
||||
cloud_coverage?: number;
|
||||
uv_index?: number;
|
||||
pressure?: number;
|
||||
pressure_unit?: string;
|
||||
wind_bearing?: number;
|
||||
wind_gust_speed?: number;
|
||||
wind_speed?: number;
|
||||
wind_speed_unit?: string;
|
||||
visibility_unit?: string;
|
||||
precipitation_unit?: string;
|
||||
attribution?: string;
|
||||
access_token?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fps?: number;
|
||||
bitrate?: number;
|
||||
channel_id?: number;
|
||||
entity_picture?: string;
|
||||
motion_detection?: boolean;
|
||||
frontend_stream_type?: string;
|
||||
options?: string[];
|
||||
state_class?: StateClass;
|
||||
auto_update?: boolean;
|
||||
installed_version?: string;
|
||||
in_progress?: boolean;
|
||||
latest_version?: null | string;
|
||||
release_summary?: null | string;
|
||||
release_url?: null | string;
|
||||
skipped_version?: null;
|
||||
title?: null;
|
||||
Count?: number;
|
||||
preset_modes?: null;
|
||||
percentage?: number;
|
||||
percentage_step?: number;
|
||||
preset_mode?: null;
|
||||
active?: boolean;
|
||||
color?: string;
|
||||
k_value?: number;
|
||||
name?: string;
|
||||
nozzle_temp_min?: string;
|
||||
nozzle_temp_max?: string;
|
||||
type?: string;
|
||||
modifier?: number;
|
||||
'AMS Slot 1'?: number;
|
||||
remain?: number;
|
||||
tag_uid?: string;
|
||||
tray_uuid?: string;
|
||||
hvac_modes?: string[];
|
||||
min_temp?: number;
|
||||
max_temp?: number;
|
||||
current_temperature?: number;
|
||||
hvac_action?: string;
|
||||
occupied_cooling_setpoint?: number;
|
||||
occupied_heating_setpoint?: number;
|
||||
system_mode?: string;
|
||||
unoccupied_heating_setpoint?: number;
|
||||
off_with_transition?: boolean;
|
||||
off_brightness?: number | null;
|
||||
battery_size?: string;
|
||||
battery_quantity?: number;
|
||||
battery_voltage?: number;
|
||||
measurement_type?: MeasurementType;
|
||||
device_type?: string;
|
||||
status?: string;
|
||||
zcl_unit_of_measurement?: number;
|
||||
last_triggered?: Date;
|
||||
current?: number;
|
||||
restored?: boolean;
|
||||
source_list?: string[];
|
||||
}
|
||||
|
||||
export enum ColorMode {
|
||||
Brightness = 'brightness',
|
||||
ColorTemp = 'color_temp',
|
||||
Onoff = 'onoff',
|
||||
Xy = 'xy'
|
||||
}
|
||||
|
||||
export enum MeasurementType {
|
||||
ActiveMeasurement = 'ACTIVE_MEASUREMENT',
|
||||
ActiveMeasurementPhaseAMeasurement = 'ACTIVE_MEASUREMENT, PHASE_A_MEASUREMENT'
|
||||
}
|
||||
|
||||
export enum Mode {
|
||||
Auto = 'auto',
|
||||
Box = 'box',
|
||||
Single = 'single',
|
||||
Slider = 'slider'
|
||||
}
|
||||
|
||||
export enum StateClass {
|
||||
Measurement = 'measurement',
|
||||
TotalIncreasing = 'total_increasing'
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
id: string;
|
||||
parent_id: null | string;
|
||||
user_id: null | string;
|
||||
}
|
||||
8
src/lib/interfaces/printer.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface Filament {
|
||||
Hex: string;
|
||||
Color: string;
|
||||
Material: string;
|
||||
Weight: string;
|
||||
Count: number;
|
||||
Link: string;
|
||||
}
|
||||
79
src/lib/interfaces/proxmox.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export interface Cluster {
|
||||
id: string;
|
||||
quorate: number;
|
||||
version: number;
|
||||
type: string;
|
||||
nodes: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Memory {
|
||||
used: number;
|
||||
free: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface RootFs {
|
||||
used: number;
|
||||
free: number;
|
||||
avail: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface Swap {
|
||||
total: number;
|
||||
free: number;
|
||||
used: number;
|
||||
}
|
||||
|
||||
interface CpuInfo {
|
||||
cores: number;
|
||||
mhz: string;
|
||||
cpus: number;
|
||||
model: string;
|
||||
sockets: number;
|
||||
user_hz: number;
|
||||
flags: string;
|
||||
hvm: string;
|
||||
}
|
||||
|
||||
interface KernelInfo {
|
||||
release: string;
|
||||
sysname: string;
|
||||
version: string;
|
||||
machine: string;
|
||||
}
|
||||
|
||||
interface BootInfo {
|
||||
secureboot?: number;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
interface NodeStatus {
|
||||
memory: Memory;
|
||||
kversion: string;
|
||||
cpu: number;
|
||||
ksm: { shared: number };
|
||||
uptime: number;
|
||||
currentKernel: KernelInfo;
|
||||
rootfs: RootFs;
|
||||
swap: Swap;
|
||||
idle: number;
|
||||
cpuinfo: CpuInfo;
|
||||
pveversion: string;
|
||||
loadavg: [string, string, string];
|
||||
wait: number;
|
||||
bootInfo: BootInfo;
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
info: NodeStatus;
|
||||
online: number;
|
||||
nodeid: number;
|
||||
local: number;
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
ip: string;
|
||||
level: string;
|
||||
}
|
||||
92
src/lib/server/filament.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { Filament } from '$lib/interfaces/printer';
|
||||
|
||||
const filament: Filament[] = [
|
||||
{
|
||||
Hex: '#DD4344',
|
||||
Color: 'Scarlet Red',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 2,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742848731'
|
||||
},
|
||||
{
|
||||
Hex: '#61C57F',
|
||||
Color: 'Grass Green',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 2,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742783195'
|
||||
},
|
||||
{
|
||||
Hex: '#F7DA5A',
|
||||
Color: 'Lemon Yellow',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 2,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742717659'
|
||||
},
|
||||
{
|
||||
Hex: '#E8DBB7',
|
||||
Color: 'Desert Tan',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 1,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=48612736401756'
|
||||
},
|
||||
{
|
||||
Hex: "url('https://www.transparenttextures.com/patterns/asfalt-dark.png'",
|
||||
Color: 'White Marble',
|
||||
Material: 'PLA Marble',
|
||||
Weight: '1kg',
|
||||
Count: 1,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/products/pla-marble?variant=43964050964699'
|
||||
},
|
||||
{
|
||||
Hex: '#0078C0',
|
||||
Color: 'Marine Blue',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 1,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996751073499'
|
||||
},
|
||||
{
|
||||
Hex: '#000000',
|
||||
Color: 'Charcoal',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 2,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742750427'
|
||||
},
|
||||
{
|
||||
Hex: '#ffffff',
|
||||
Color: 'Ivory White',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 2,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742586587'
|
||||
},
|
||||
{
|
||||
Hex: '#E8AFCE',
|
||||
Color: 'Sakura Pink',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 1,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742684891'
|
||||
},
|
||||
{
|
||||
Hex: '#AE96D5',
|
||||
Color: 'Lilac Purple',
|
||||
Material: 'PLA Matte',
|
||||
Weight: '1kg',
|
||||
Count: 1,
|
||||
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742914267'
|
||||
}
|
||||
];
|
||||
|
||||
export function filamentByColor(name: string) {
|
||||
return filament.find((f) => f.Color?.toLowerCase() === name?.toLowerCase());
|
||||
}
|
||||
|
||||
export function currentFilament(): Filament[] {
|
||||
return filament;
|
||||
}
|
||||
36
src/lib/server/homeassistant.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { Entity } from '$lib/interfaces/homeassistant';
|
||||
|
||||
function buildHomeassistantRequest() {
|
||||
const url = env.HOMEASSISTANT_URL || '';
|
||||
const token = env.HOMEASSISTANT_TOKEN || '';
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
return { url, options };
|
||||
}
|
||||
|
||||
async function fetchHassStates() {
|
||||
const { url, options } = buildHomeassistantRequest();
|
||||
return fetch(url, options).then((resp) => resp.json());
|
||||
}
|
||||
|
||||
export async function fetchP1P(): Promise<Entity[]> {
|
||||
try {
|
||||
let hassStates = await fetchHassStates();
|
||||
|
||||
hassStates = hassStates.filter(
|
||||
(el: Entity) => el.attributes.friendly_name?.includes('P1P') === true
|
||||
);
|
||||
return hassStates;
|
||||
} catch (error) {
|
||||
console.log('ERROR! from fetchP1P:', error);
|
||||
return Promise.reject(null);
|
||||
}
|
||||
}
|
||||
116
src/lib/server/kubernetes.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import * as k8s from '@kubernetes/client-node';
|
||||
import stream from 'stream';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const context = {
|
||||
name: 'kazan-insecure',
|
||||
user: 'admin',
|
||||
cluster: 'kazan-insecure'
|
||||
};
|
||||
|
||||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromDefault({ contexts: [context] });
|
||||
|
||||
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
|
||||
const appsV1Api = kc.makeApiClient(k8s.AppsV1Api);
|
||||
|
||||
const k8sLog = new k8s.Log(kc);
|
||||
|
||||
export async function getReplicas() {
|
||||
try {
|
||||
const allReplicas = await appsV1Api.listReplicaSetForAllNamespaces();
|
||||
return allReplicas.items;
|
||||
} catch (error) {
|
||||
console.log('error when getting replicas:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPods() {
|
||||
try {
|
||||
const allPods = await k8sApi.listPodForAllNamespaces();
|
||||
return allPods.items;
|
||||
} catch (error) {
|
||||
console.log('error when getting k8s resources:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPod(name: string, namespace: string) {
|
||||
try {
|
||||
return await k8sApi.readNamespacedPodTemplate({ name, namespace });
|
||||
} catch (error) {
|
||||
console.log(`error when getting pod:`, error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDeployments() {
|
||||
try {
|
||||
const allDeploys = await appsV1Api.listDeploymentForAllNamespaces();
|
||||
return allDeploys.items;
|
||||
} catch (error) {
|
||||
console.log('error when getting deployments:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDaemons() {
|
||||
try {
|
||||
const allDaemons = await appsV1Api.listDaemonSetForAllNamespaces();
|
||||
return allDaemons.items;
|
||||
} catch (error) {
|
||||
console.log('error when getting daemons:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNodes() {
|
||||
try {
|
||||
const nodes = await k8sApi.listNode();
|
||||
return nodes.items;
|
||||
} catch (error) {
|
||||
console.log('error when getting k8s nodes:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function createLogStream(podName: string, namespace: string, containerName: string) {
|
||||
// const logEmitter = new EventTarget(); // use EventTarget or EventEmitter
|
||||
const logEmitter = writable();
|
||||
|
||||
const maxLines = 400;
|
||||
let liveStream: stream.PassThrough | null = null;
|
||||
let logAbortController;
|
||||
|
||||
async function start() {
|
||||
// Live logs
|
||||
liveStream = new stream.PassThrough();
|
||||
liveStream.on('data', (chunk) => {
|
||||
let chunks = chunk.toString().split('\n').filter(Boolean);
|
||||
|
||||
console.log('chynjks length:', chunks?.length);
|
||||
if (chunks?.length > maxLines) {
|
||||
chunks = chunks.slice(maxLines);
|
||||
}
|
||||
|
||||
chunks.forEach((line) => logEmitter.set(line));
|
||||
});
|
||||
|
||||
console.log('setting logAbortController, prev:', logAbortController);
|
||||
logAbortController = await k8sLog.log(namespace, podName, containerName, liveStream, {
|
||||
follow: true,
|
||||
timestamps: false,
|
||||
pretty: false,
|
||||
tailLines: maxLines
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
console.log('ending livestream!!');
|
||||
logAbortController?.abort();
|
||||
liveStream?.end();
|
||||
}
|
||||
|
||||
return { start, stop, logEmitter };
|
||||
}
|
||||
86
src/lib/server/proxmox.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { Cluster, Node } from '$lib/interfaces/proxmox';
|
||||
|
||||
function buildProxmoxRequest() {
|
||||
const url = env.PROXMOX_URL || 'https://10.0.0.50:8006/api2/json/';
|
||||
const token = env.PROXMOX_TOKEN || 'REPLACE_WITH_PROXMOX_TOKEN';
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
return { url, options };
|
||||
}
|
||||
|
||||
async function fetchNodeVMs(node: Node) {
|
||||
const r = buildProxmoxRequest();
|
||||
r.url += 'nodes/' + node?.name + '/qemu';
|
||||
|
||||
return fetch(r.url, r?.options)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async function fetchNodeLXCs(node: Node) {
|
||||
const r = buildProxmoxRequest();
|
||||
r.url += 'nodes/' + node?.name + '/lxc';
|
||||
|
||||
return fetch(r.url, r?.options)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async function fetchNodeInfo(node: Node) {
|
||||
const r = buildProxmoxRequest();
|
||||
r.url += 'nodes/' + node?.name + '/status';
|
||||
|
||||
return fetch(r.url, r?.options)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async function getClusterInfo() {
|
||||
const r = buildProxmoxRequest();
|
||||
r.url += 'cluster/status';
|
||||
|
||||
return fetch(r.url, r?.options)
|
||||
.then((resp) => resp.json())
|
||||
.then((response) => {
|
||||
const { data } = response;
|
||||
const cluster = data.filter((d: Node | Cluster) => d?.type === 'cluster')[0];
|
||||
const nodes = data.filter((d: Node | Cluster) => d?.type === 'node');
|
||||
|
||||
return { cluster, nodes };
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchNodes(): Promise<{ nodes: Node[]; cluster: Cluster | null }> {
|
||||
try {
|
||||
const { nodes, cluster } = await getClusterInfo();
|
||||
|
||||
const infoP = Promise.all(nodes.map((node: Node) => fetchNodeInfo(node)));
|
||||
const vmsP = Promise.all(nodes.map((node: Node) => fetchNodeVMs(node)));
|
||||
const lxcsP = Promise.all(nodes.map((node: Node) => fetchNodeLXCs(node)));
|
||||
|
||||
const [info, vms, lxcs] = await Promise.all([infoP, vmsP, lxcsP]);
|
||||
|
||||
return {
|
||||
cluster,
|
||||
nodes: nodes.map((node: Node, i: number) => {
|
||||
return {
|
||||
...node,
|
||||
info: info[i],
|
||||
vms: vms[i],
|
||||
lxcs: lxcs[i]
|
||||
};
|
||||
})
|
||||
};
|
||||
} catch (error) {
|
||||
console.log('ERROR from fetchnodes:', error);
|
||||
|
||||
return { nodes: [], cluster: null };
|
||||
}
|
||||
}
|
||||
22
src/lib/server/traefik.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
const TRAEFIK_HTTP_URL = '/api/http';
|
||||
|
||||
function buildTraefikRequest(path: string) {
|
||||
const baseURL = env.TRAEFIK_URL || 'http://localhost:9000';
|
||||
const url = `${baseURL}${TRAEFIK_HTTP_URL}/${path}`;
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
return { url, options };
|
||||
}
|
||||
|
||||
export async function getRouters() {
|
||||
const { url, options } = buildTraefikRequest('routers');
|
||||
|
||||
return fetch(url, options).then((resp) => resp.json());
|
||||
}
|
||||
46
src/lib/utils/conversion.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
export function formatBytes(bytes: number) {
|
||||
if (bytes < 1024) return '0 KB'; // Ensure we don't show bytes, only KB and above
|
||||
|
||||
const units = ['KB', 'MB', 'GB', 'TB'];
|
||||
let unitIndex = -1;
|
||||
let formattedSize = bytes;
|
||||
|
||||
do {
|
||||
formattedSize /= 1024;
|
||||
unitIndex++;
|
||||
} while (formattedSize >= 1024 && unitIndex < units.length - 1);
|
||||
|
||||
return `${formattedSize.toFixed(2)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
export function formatDuration(seconds: number) {
|
||||
if (seconds === 0) return 'Uptime: 0 days 00:00:00';
|
||||
|
||||
const days = Math.floor(seconds / 86400);
|
||||
seconds %= 86400;
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
seconds %= 3600;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
seconds %= 60;
|
||||
|
||||
return `${days} days ${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(Math.floor(seconds)).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function convertKiToHumanReadable(input: string) {
|
||||
const match = input.match(/^(\d+)(Ki)$/);
|
||||
if (!match) return 'Invalid input';
|
||||
|
||||
const kibibytes = parseInt(match[1], 10);
|
||||
const bytes = kibibytes * 1024;
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
let i = 0;
|
||||
let humanReadable = bytes;
|
||||
|
||||
while (humanReadable >= 1024 && i < sizes.length - 1) {
|
||||
humanReadable /= 1024;
|
||||
i++;
|
||||
}
|
||||
|
||||
return `${humanReadable.toFixed(2)} ${sizes[i]}`;
|
||||
}
|
||||
3
src/lib/utils/string.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function capitalizeFirstLetter(text: string) {
|
||||
return text.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
}
|
||||
37
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
<Header />
|
||||
|
||||
<div class="content">
|
||||
<Sidebar />
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
padding-top: 1.3rem;
|
||||
padding-bottom: 3rem;
|
||||
|
||||
main {
|
||||
/* mobile: 1rem */
|
||||
margin: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
src/routes/+page.svelte
Normal file
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
</script>
|
||||
|
||||
<PageHeader>Welcome to SvelteKit</PageHeader>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Pariatur laboriosam, necessitatibus
|
||||
repudiandae quo voluptatem ratione, sit eum dolores enim earum at officia expedita consequuntur
|
||||
ipsum voluptatum sunt, atque magni minus.
|
||||
</p>
|
||||
90
src/routes/cluster/+page.server.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getPods, getNodes, getDeployments, getDaemons } from '$lib/server/kubernetes';
|
||||
import type { V1DaemonSet, V1Deployment, V1Node, V1Pod } from '@kubernetes/client-node';
|
||||
|
||||
function filterAndStackNodes(nodes: V1Node[], pods: V1Pod[]) {
|
||||
const getNode = (name: string) => nodes.find((node) => node.metadata?.name === name);
|
||||
|
||||
pods.forEach((pod) => {
|
||||
if (!pod.spec?.nodeName) return;
|
||||
const node = getNode(pod.spec.nodeName);
|
||||
node.pods.push(pod);
|
||||
});
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function filterAndStackDaemons(daemons: V1DaemonSet[], pods: V1Pod[]) {
|
||||
const getDaemon = (name: string) =>
|
||||
daemons.find((daemon: V1DaemonSet) => daemon.metadata?.name === name);
|
||||
|
||||
pods.forEach((pod: V1Pod) => {
|
||||
if (!pod.metadata?.ownerReferences?.[0].name) return;
|
||||
|
||||
const daemon = getDaemon(pod.metadata.ownerReferences[0].name);
|
||||
if (!daemon) return;
|
||||
daemon?.pods.push(pod);
|
||||
});
|
||||
|
||||
return daemons;
|
||||
}
|
||||
|
||||
function filterAndStackDeploys(deploys: V1Deployment[], pods: V1Pod[]) {
|
||||
const getDeploy = (name: string) =>
|
||||
deploys.find((deploy) => {
|
||||
return (
|
||||
(deploy.spec?.selector.matchLabels?.app &&
|
||||
deploy.spec.selector.matchLabels?.app === name) ||
|
||||
(deploy.metadata?.labels?.['app.kubernetes.io/name'] &&
|
||||
deploy.metadata.labels['app.kubernetes.io/name'] === name) ||
|
||||
(deploy.metadata?.labels?.['k8s-app'] && deploy.metadata.labels['k8s-app'] === name)
|
||||
);
|
||||
});
|
||||
|
||||
pods.forEach((pod) => {
|
||||
const name =
|
||||
pod.metadata?.labels?.['k8s-app'] ||
|
||||
pod.metadata?.labels?.['app.kubernetes.io/name'] ||
|
||||
pod.metadata?.labels?.app ||
|
||||
'not found';
|
||||
|
||||
const deploy = getDeploy(name);
|
||||
if (!deploy) return;
|
||||
deploy.pods.push(pod);
|
||||
});
|
||||
|
||||
return deploys;
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const [podsResp, nodesResp, deployResp, daemonsResp] = await Promise.all([
|
||||
getPods(),
|
||||
getNodes(),
|
||||
getDeployments(),
|
||||
getDaemons()
|
||||
]);
|
||||
|
||||
const pods: V1Pod[] = JSON.parse(JSON.stringify(podsResp));
|
||||
let nodes: V1Node[] = JSON.parse(JSON.stringify(nodesResp));
|
||||
let deployments: V1Deployment[] = JSON.parse(JSON.stringify(deployResp));
|
||||
let daemons: V1DaemonSet[] = JSON.parse(JSON.stringify(daemonsResp));
|
||||
|
||||
nodes.forEach((node) => (node['pods'] = []));
|
||||
deployments.forEach((deploy) => (deploy['pods'] = []));
|
||||
daemons.forEach((daemon) => (daemon['pods'] = []));
|
||||
nodes = filterAndStackNodes(nodes, pods);
|
||||
deployments = filterAndStackDeploys(deployments, pods);
|
||||
daemons = filterAndStackDaemons(daemons, pods);
|
||||
|
||||
// TODO move to frontend
|
||||
deployments = deployments.sort((a, b) =>
|
||||
a.metadata?.name && b.metadata?.name && a.metadata?.name > b.metadata?.name ? 1 : -1
|
||||
);
|
||||
|
||||
return {
|
||||
nodes,
|
||||
deployments,
|
||||
daemons,
|
||||
pods
|
||||
};
|
||||
};
|
||||
94
src/routes/cluster/+page.svelte
Normal file
@@ -0,0 +1,94 @@
|
||||
<script lang="ts">
|
||||
import Node from '$lib/components/Node.svelte';
|
||||
import Deploy from '$lib/components/Deploy.svelte';
|
||||
import Daemon from '$lib/components/Daemon.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import type { V1DaemonSet, V1Deployment, V1Node } from '@kubernetes/client-node';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const deployments: V1Deployment[] = data?.deployments;
|
||||
const daemons: V1DaemonSet[] = data?.daemons;
|
||||
const nodes: V1Node[] = data?.nodes;
|
||||
</script>
|
||||
|
||||
<PageHeader>Cluster overview</PageHeader>
|
||||
|
||||
<details open>
|
||||
<summary>
|
||||
<h2>Cluster <span>{nodes.length} nodes</span></h2>
|
||||
</summary>
|
||||
<div class="server-list">
|
||||
{#each nodes as node (node)}
|
||||
<Node {node} />
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>
|
||||
<h2>
|
||||
Daemons <span
|
||||
>{daemons.length} daemons ({daemons.reduce(
|
||||
(total, item) => total + (item.pods ? item.pods.length : 0),
|
||||
0
|
||||
)} pods)</span
|
||||
>
|
||||
</h2>
|
||||
</summary>
|
||||
|
||||
<div class="server-list deploys">
|
||||
{#each daemons as daemon (daemon)}
|
||||
<Daemon {daemon} />
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>
|
||||
<h2>
|
||||
Pods <span
|
||||
>{deployments.length} deployments ({deployments.reduce(
|
||||
(total, item) => total + (item.pods ? item.pods.length : 0),
|
||||
0
|
||||
)} pods)</span
|
||||
>
|
||||
</h2>
|
||||
</summary>
|
||||
|
||||
<div class="server-list deploys">
|
||||
{#each deployments as deploy (deploy)}
|
||||
<Deploy {deploy} />
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<style lang="scss">
|
||||
.server-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.server-list.deploys {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
details summary::-webkit-details-marker,
|
||||
details summary::marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details > summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Reckless Neue';
|
||||
}
|
||||
</style>
|
||||
17
src/routes/cluster/pod/[uid]/+page.server.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getPods } from '$lib/server/kubernetes';
|
||||
import type { V1Pod } from '@kubernetes/client-node';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const { uid } = params;
|
||||
|
||||
console.log(uid);
|
||||
|
||||
const podsResp = await getPods();
|
||||
const pods: V1Pod[] = JSON.parse(JSON.stringify(podsResp));
|
||||
|
||||
const pod = pods.find((p) => p.metadata?.uid === uid);
|
||||
return {
|
||||
pod
|
||||
};
|
||||
};
|
||||
165
src/routes/cluster/pod/[uid]/+page.svelte
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import type { PageData } from './$types';
|
||||
import type { V1Pod } from '@kubernetes/client-node';
|
||||
import Tab from '$lib/components/navigation/Tab.svelte';
|
||||
import Tabs from '$lib/components/navigation/Tabs.svelte';
|
||||
import TabList from '$lib/components/navigation/TabList.svelte';
|
||||
import TabView from '$lib/components/navigation/TabView.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Section from '$lib/components/Section.svelte';
|
||||
import { formatDuration } from '$lib/utils/conversion';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import Logs from '$lib/components/Logs.svelte';
|
||||
import { source } from 'sveltekit-sse';
|
||||
|
||||
let value = 'no data :(';
|
||||
if (browser) {
|
||||
console.log('setting up sse', window.location.pathname);
|
||||
value = source(window.location.pathname + '/logs').select('message');
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
const { pod }: { pod: V1Pod | undefined } = data;
|
||||
const { status, metadata, spec } = pod || {};
|
||||
// console.log(pod);
|
||||
|
||||
let uptime = writable(new Date().getTime() - new Date(status?.startTime || 0).getTime());
|
||||
|
||||
let logs = writable([]);
|
||||
let eventSource: EventSource;
|
||||
|
||||
function setupWS() {
|
||||
const url = new URL(`${window.location.origin}/cluster/pod/${pod?.metadata?.uid}/logs`);
|
||||
if (pod?.metadata) {
|
||||
console.error('missing pod info. not enough metadata to setup WS connection.');
|
||||
return;
|
||||
}
|
||||
|
||||
url.searchParams.append('pod', pod?.metadata?.name || '');
|
||||
url.searchParams.append('namespace', pod?.metadata?.namespace || '');
|
||||
url.searchParams.append('container', pod?.spec?.containers[0].name || '');
|
||||
|
||||
eventSource = new EventSource(url);
|
||||
eventSource.onmessage = function (event) {
|
||||
logs.update((arr) => arr.concat(event.data));
|
||||
};
|
||||
|
||||
return (eventSource.onerror = (err) => {
|
||||
console.error('EventSource failed:', err);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setInterval(() => uptime.update((n) => n + 1000), 1000);
|
||||
return setupWS();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
console.log('EventSource closed');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<PageHeader>Pod: {pod?.metadata?.name}</PageHeader>
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Logs</Tab>
|
||||
<Tab>Metadata</Tab>
|
||||
<Tab>Deployment logs</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabView>
|
||||
<div class="section-wrapper">
|
||||
<Section title="Status" description="">
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Phase</label>
|
||||
<span>{status?.phase}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Pod IP</label>
|
||||
<span>{status?.podIP}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>QoS Class</label>
|
||||
<span>{status?.qosClass}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Running for</label>
|
||||
<span>{formatDuration($uptime / 1000)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Metadata" description="">
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Namespace</label>
|
||||
<span>{metadata?.namespace}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Parent resource</label>
|
||||
<span>{metadata?.ownerReferences?.[0].kind}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Spec" description="">
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Node name</label>
|
||||
<span>{spec?.nodeName}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Restart policy</label>
|
||||
<span>{spec?.restartPolicy}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Service account</label>
|
||||
<span>{spec?.serviceAccount}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Scheduler</label>
|
||||
<span>{spec?.schedulerName}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Host network</label>
|
||||
<span>{spec?.hostNetwork ? 'yes' : 'no'}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>DNS policy</label>
|
||||
<span>{spec?.dnsPolicy}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
</TabView>
|
||||
|
||||
<TabView>
|
||||
<Logs logs={$logs} stream={true} />
|
||||
</TabView>
|
||||
|
||||
<TabView>
|
||||
<Logs logs={JSON.stringify(metadata, null, 2).split('\n')} lineNumbers={false} />
|
||||
</TabView>
|
||||
|
||||
<TabView>
|
||||
<Logs lineNumbers={false} />
|
||||
</TabView>
|
||||
</Tabs>
|
||||
27
src/routes/cluster/pod/[uid]/logs/+server.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createLogStream } from '$lib/server/kubernetes';
|
||||
import { produce } from 'sveltekit-sse';
|
||||
|
||||
export function GET({ request }) {
|
||||
return produce(async function start({ emit }) {
|
||||
console.log('----- REQUEST -----');
|
||||
const url = new URL(request.url);
|
||||
const pod = url.searchParams.get('pod');
|
||||
const namespace = url.searchParams.get('namespace');
|
||||
const container = url.searchParams.get('container');
|
||||
|
||||
console.log('pod, namespace:', pod, namespace);
|
||||
const k8sLogs = createLogStream(pod, namespace, container);
|
||||
k8sLogs.start();
|
||||
const unsubscribe = k8sLogs.logEmitter.subscribe((msg: string) => {
|
||||
emit('message', msg);
|
||||
});
|
||||
|
||||
const { error } = emit('message', `the time is ${Date.now()}`);
|
||||
|
||||
if (error) {
|
||||
k8sLogs.stop();
|
||||
unsubscribe();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
20
src/routes/health/+page.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Table from '$lib/components/Table.svelte';
|
||||
|
||||
let columns = ['Domain', 'Status'];
|
||||
let data = [
|
||||
{ Domain: 'laravel-ucm1d.kinsta.app', Status: 'Live' },
|
||||
{ Domain: 'laravel-ucm1d.kinsta.app', Status: 'Live' },
|
||||
{ Domain: 'laravel-ucm1d.kinsta.app', Status: 'Live' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<PageHeader>Health</PageHeader>
|
||||
|
||||
<Table
|
||||
title="Domains list"
|
||||
description="All of the verified domains below point to this application and are covered by free Cloudflare SSL certificates for a secure HTTPS connection. The DNS records for the domains must be set up correctly for them to work."
|
||||
{columns}
|
||||
{data}
|
||||
/>
|
||||
26
src/routes/network/+page.server.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getRouters } from '$lib/server/traefik';
|
||||
|
||||
let cache = {
|
||||
timestamp: 0,
|
||||
data: null
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const now = Date.now();
|
||||
|
||||
if (cache.data && now - cache.timestamp < 10000) {
|
||||
console.log('Serving from cache');
|
||||
return {
|
||||
routers: cache.data
|
||||
};
|
||||
}
|
||||
|
||||
const routers = await getRouters();
|
||||
|
||||
cache = { timestamp: now, data: routers };
|
||||
|
||||
return {
|
||||
routers
|
||||
};
|
||||
};
|
||||
56
src/routes/network/+page.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Section from '$lib/components/Section.svelte';
|
||||
import Table from '$lib/components/Table.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { routers } = data;
|
||||
const columns = {
|
||||
entryPoints: 'Entrypoints',
|
||||
name: 'Name',
|
||||
provider: 'Provider',
|
||||
rule: 'Rule',
|
||||
service: 'Service',
|
||||
status: 'Status'
|
||||
};
|
||||
const links: string[] = routers.map((router) => `/network/${router.service}`);
|
||||
|
||||
const providers = [
|
||||
...new Set(
|
||||
routers.map((item) => item.provider).filter((provider) => typeof provider === 'string')
|
||||
)
|
||||
];
|
||||
console.log(routers);
|
||||
</script>
|
||||
|
||||
<PageHeader>Network</PageHeader>
|
||||
|
||||
<div class="section-wrapper">
|
||||
<Section title="Traefik" description="Treafik is a network proxy and webserver.">
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Number of routers</label>
|
||||
<span>{routers.length}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Providers</label>
|
||||
<span>{providers?.join(', ')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Table title="Routers" description="Traefik routers available" {columns} data={routers} {links} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.server-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: left;
|
||||
gap: 2rem;
|
||||
}
|
||||
</style>
|
||||
29
src/routes/network/[id]/+page.server.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getRouters } from '$lib/server/traefik';
|
||||
|
||||
const cache = {
|
||||
timestamp: 0,
|
||||
data: {}
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const now = Date.now();
|
||||
const { id } = params;
|
||||
|
||||
if (cache.data[id] && now - cache.timestamp < 10000) {
|
||||
console.log('Serving from cache');
|
||||
return {
|
||||
router: cache.data[id]
|
||||
};
|
||||
}
|
||||
|
||||
const routers = await getRouters();
|
||||
const router = routers.find((router) => router.service === id);
|
||||
|
||||
cache.time = now;
|
||||
cache.data[id] = router;
|
||||
|
||||
return {
|
||||
router
|
||||
};
|
||||
};
|
||||
15
src/routes/network/[id]/+page.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { router } = data;
|
||||
</script>
|
||||
|
||||
<PageHeader>Network: {router.service}</PageHeader>
|
||||
|
||||
<div>
|
||||
<p>router:</p>
|
||||
<pre><code>{JSON.stringify(router, null, 2)}</code></pre>
|
||||
</div>
|
||||
11
src/routes/printer/+page.server.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { fetchP1P } from '$lib/server/homeassistant';
|
||||
import { currentFilament } from '$lib/server/filament';
|
||||
import type { Entity } from '$lib/interfaces/homeassistant';
|
||||
import type { Filament } from '$lib/interfaces/printer';
|
||||
|
||||
export const load: PageServerLoad = async (): Promise<{ p1p: Entity[]; filament: Filament[] }> => {
|
||||
const p1p = await fetchP1P();
|
||||
const filament = currentFilament();
|
||||
return { p1p, filament };
|
||||
};
|
||||
237
src/routes/printer/+page.svelte
Normal file
@@ -0,0 +1,237 @@
|
||||
<script lang="ts">
|
||||
import { capitalizeFirstLetter } from '$lib/utils/string';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Section from '$lib/components/Section.svelte';
|
||||
import Table from '$lib/components/Table.svelte';
|
||||
import Progress from '$lib/components/Progress.svelte';
|
||||
|
||||
import Finished from '$lib/icons/finished.svelte';
|
||||
import Paused from '$lib/icons/paused.svelte';
|
||||
import Stopped from '$lib/icons/stopped.svelte';
|
||||
import Printing from '$lib/icons/printing.svelte';
|
||||
import PrinterIdle from '$lib/icons/printer-idle.svelte';
|
||||
import PrinterPaused from '$lib/icons/printer-paused.svelte';
|
||||
import PrinterPrinting from '$lib/icons/printer-printing.svelte';
|
||||
import PrinterStopped from '$lib/icons/printer-stopped.svelte';
|
||||
import NozzleTemperature from '$lib/icons/temperature-nozzle.svelte';
|
||||
import BedTemperature from '$lib/icons/temperature-bed.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import type { Entity } from '$lib/interfaces/homeassistant';
|
||||
import type { Filament } from '$lib/interfaces/printer';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
const p1p: Entity[] = data?.p1p;
|
||||
const filament: Filament[] = data?.filament;
|
||||
|
||||
const currentStage = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_current_stage'
|
||||
)[0];
|
||||
const printStatus = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_print_status'
|
||||
)[0];
|
||||
const bedTemp = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_bed_temperature'
|
||||
)[0];
|
||||
const nozzleTemp = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_nozzle_temperature'
|
||||
)[0];
|
||||
const totalUsage = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_total_usage'
|
||||
)[0];
|
||||
const nozzleType = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_nozzle_type'
|
||||
)[0];
|
||||
const nozzleSize = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_nozzle_size'
|
||||
)[0];
|
||||
const bedType = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_print_bed_type'
|
||||
)[0];
|
||||
const currentLayer = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_current_layer'
|
||||
)[0];
|
||||
const totalLayer = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_total_layer_count'
|
||||
)[0];
|
||||
const progress = p1p.filter(
|
||||
(el) => el.entity_id === 'sensor.p1p_01s00c370700273_print_progress'
|
||||
)[0];
|
||||
|
||||
console.log(p1p);
|
||||
|
||||
let columns = ['Hex', 'Color', 'Material', 'Weight', 'Count', 'Link'];
|
||||
const links = filament.map((f) => `/printer/${f.Color.replaceAll(' ', '-').toLowerCase()}`);
|
||||
|
||||
const iconDictStage = {
|
||||
idle: PrinterIdle,
|
||||
printing: PrinterPrinting,
|
||||
paused: PrinterPaused,
|
||||
stopped: PrinterStopped,
|
||||
heatbed_preheating: PrinterPrinting,
|
||||
cleaning_nozzle_tip: PrinterPrinting,
|
||||
homing_toolhead: PrinterPrinting
|
||||
};
|
||||
const iconDictState = { running: Printing, pause: Paused, failed: Stopped, finish: Finished };
|
||||
|
||||
interface FilamentUpdated {
|
||||
date: Date;
|
||||
title?: string;
|
||||
}
|
||||
const lastUpdateFilament: FilamentUpdated = {
|
||||
date: new Date('2025-04-01T05:47:01+00:00')
|
||||
};
|
||||
lastUpdateFilament.title = lastUpdateFilament.date
|
||||
.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
})
|
||||
.toLowerCase();
|
||||
</script>
|
||||
|
||||
<PageHeader>Printer</PageHeader>
|
||||
|
||||
<div class="section-wrapper">
|
||||
<Section
|
||||
title="Printer status"
|
||||
description="Historical printer information, last prints and current status."
|
||||
>
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Current stage</label>
|
||||
<span
|
||||
><span class="icon">
|
||||
<svelte:component this={iconDictStage[currentStage.state]} />
|
||||
</span>{currentStage.state}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Bed temperature</label>
|
||||
<span
|
||||
><span class="icon"><BedTemperature /></span>{bedTemp.state}
|
||||
{bedTemp.attributes.unit_of_measurement}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Nozzle temperature</label>
|
||||
<span
|
||||
><span class="icon"><NozzleTemperature /></span>{nozzleTemp.state}
|
||||
{nozzleTemp.attributes.unit_of_measurement}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Print status</label>
|
||||
<span>
|
||||
<span class={`icon ${printStatus?.state === 'running' ? 'spin' : ''}`}>
|
||||
<svelte:component this={iconDictState[printStatus.state]} /></span
|
||||
>
|
||||
{printStatus.state}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<Progress value={progress.state} />
|
||||
{#if currentLayer.state !== totalLayer.state}
|
||||
<span>Currently printing layer line {currentLayer.state} of {totalLayer.state}</span>
|
||||
{:else}
|
||||
<span>Finished printing {currentLayer.state} of {totalLayer.state} layers!</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Printer attributes"
|
||||
description="Historical printer information, last prints and current status."
|
||||
>
|
||||
<div class="section-row">
|
||||
<div class="section-element">
|
||||
<label>Total print time</label>
|
||||
<span>
|
||||
{Math.floor(Number(totalUsage.state) * 10) / 10}
|
||||
<!-- {formatDuration(totalUsage.state * 3600)} -->
|
||||
{totalUsage.attributes.unit_of_measurement}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Nozzle Type</label>
|
||||
<span
|
||||
>{capitalizeFirstLetter(nozzleType.state.replaceAll('_', ' '))}
|
||||
{nozzleType.attributes.unit_of_measurement}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Nozzle Size</label>
|
||||
<span>{nozzleSize?.state} {nozzleSize.attributes.unit_of_measurement}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-element">
|
||||
<label>Bed type</label>
|
||||
<span
|
||||
>{capitalizeFirstLetter(bedType?.state?.replaceAll('_', ' ') || 'not found')}
|
||||
{bedType?.attributes.unit_of_measurement}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img src="/printer.png" />
|
||||
</Section>
|
||||
|
||||
<Table
|
||||
title="Filaments"
|
||||
description={`${filament.length} colors are currently in stock. Overview of currently stocked filament.`}
|
||||
{columns}
|
||||
data={filament}
|
||||
{links}
|
||||
footer={`Last updated on ${lastUpdateFilament.title}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
img {
|
||||
width: 120px;
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
right: 1.2rem;
|
||||
}
|
||||
|
||||
.section-element {
|
||||
.icon {
|
||||
display: inline-block;
|
||||
--size: 2rem;
|
||||
height: var(--size);
|
||||
width: var(--size);
|
||||
padding-right: 0.5rem;
|
||||
|
||||
&.spin {
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
animation: rotate 6s linear infinite;
|
||||
transform-origin: calc((var(--size) / 2) - 2px) calc(var(--size) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
12
src/routes/printer/[id]/+page.server.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { filamentByColor } from '$lib/server/filament';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
let { id } = params;
|
||||
if (id) {
|
||||
id = id.replaceAll('-', ' ');
|
||||
}
|
||||
|
||||
const filament = filamentByColor(id);
|
||||
return { id, filament };
|
||||
};
|
||||
30
src/routes/printer/[id]/+page.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
const filament = data?.filament;
|
||||
</script>
|
||||
|
||||
{#if filament !== null}
|
||||
<PageHeader>Filament: {filament?.Color}</PageHeader>
|
||||
|
||||
<div class="page">
|
||||
<div class="color-block" style={`background: ${filament?.Hex}`}></div>
|
||||
</div>
|
||||
{:else}
|
||||
<PageHeader>Filament not found!</PageHeader>
|
||||
|
||||
<p>Unable to find filament {data.id}, no swatch to display.</p>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.color-block {
|
||||
--size: 2rem;
|
||||
padding: var(--size);
|
||||
width: calc(100% - (2 * var(--size)));
|
||||
height: calc(100% - (2 * var(--size)));
|
||||
height: calc(90vh - 18rem);
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
43
src/routes/servers/+page.server.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import type { Node, Cluster } from '$lib/interfaces/proxmox';
|
||||
import { fetchNodes } from '$lib/server/proxmox';
|
||||
|
||||
const TTL = 10000; // 10 seconds
|
||||
|
||||
interface ClusterCache {
|
||||
timestamp: number;
|
||||
data: {
|
||||
nodes: Node[];
|
||||
cluster: Cluster | null;
|
||||
};
|
||||
}
|
||||
|
||||
let cache: ClusterCache = {
|
||||
timestamp: 0,
|
||||
data: {
|
||||
nodes: [],
|
||||
cluster: null
|
||||
}
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const now = Date.now();
|
||||
const hit = cache.data.cluster && cache.data.nodes?.length && now - cache.timestamp < TTL;
|
||||
|
||||
if (hit) {
|
||||
const { nodes, cluster } = cache.data;
|
||||
return { nodes, cluster };
|
||||
}
|
||||
|
||||
const { nodes, cluster } = await fetchNodes();
|
||||
nodes.sort((a: Node, b: Node) => {
|
||||
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
||||
});
|
||||
|
||||
cache = { timestamp: now, data: { nodes, cluster } };
|
||||
|
||||
return {
|
||||
nodes,
|
||||
cluster
|
||||
};
|
||||
};
|
||||
29
src/routes/servers/+page.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import ServerComp from '$lib/components/Server.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { cluster, nodes } = data;
|
||||
</script>
|
||||
|
||||
<PageHeader>Servers</PageHeader>
|
||||
|
||||
<div class="server-list">
|
||||
{#each nodes as node (node.name)}
|
||||
<div>
|
||||
<ServerComp {node} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.server-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: left;
|
||||
gap: 2rem;
|
||||
}
|
||||
</style>
|
||||
28
src/routes/sites/+page.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Section from '$lib/components/Section.svelte';
|
||||
</script>
|
||||
|
||||
<PageHeader>Sites</PageHeader>
|
||||
|
||||
<div class="section-wrapper">
|
||||
<Section
|
||||
title="Expose HTTP traffic"
|
||||
description="You can reach your Application on a specific Port you configure, redirecting all your domains to it. You can make it Private by disabling HTTP traffic."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="IP restrictions"
|
||||
description="Restrict or block access to your application based on specific IP addresses or CIDR blocks."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Expose HTTP traffic"
|
||||
description="You can reach your Application on a specific Port you configure, redirecting all your domains to it. You can make it Private by disabling HTTP traffic."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Connected services"
|
||||
description="Connected services can communicate with your application over the private network."
|
||||
/>
|
||||
</div>
|
||||
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/fonts/Inter.woff2
Normal file
BIN
static/fonts/RecklessNeue-Regular.woff2
Normal file
BIN
static/logo.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
static/logo_grey.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
static/logo_light.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
static/printer.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
146
static/style.css
Normal file
@@ -0,0 +1,146 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
src: url('/fonts/Inter.woff2') format('woff2');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: #f9f5f3;
|
||||
--color: #1c1819;
|
||||
--highlight: #eaddd5;
|
||||
|
||||
--positive: #00d439;
|
||||
--negative: #ff5449;
|
||||
--warning: #ffa312;
|
||||
|
||||
--border: 1px solid #eaddd5;
|
||||
--border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-color: var(--bg);
|
||||
color: var(--color);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
color: var(--color);
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Reckless Neue';
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--str-space-100, 8px);
|
||||
flex: 1 1 auto;
|
||||
/* align-self: center; */
|
||||
align-self: left;
|
||||
/* text-transform: capitalize; */
|
||||
flex-wrap: wrap;
|
||||
font: var(
|
||||
--str-font-heading-regular-l,
|
||||
400 1.429rem/1.4 Inter,
|
||||
Arial,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
sans-serif
|
||||
);
|
||||
margin-top: 0;
|
||||
color: var(--str-color-text-neutral-default);
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
height: unset;
|
||||
border-radius: 0.5rem;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
padding: 0 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
button span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
margin-left: -0.5rem;
|
||||
border: 1px solid #eaddd5;
|
||||
border-radius: inherit;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
transition: all ease-in-out 0.2s;
|
||||
}
|
||||
|
||||
button::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.1s ease;
|
||||
will-change: box-shadow 0.25s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button:hover span {
|
||||
border-color: #cab2aa;
|
||||
background: #f9f5f3 !important;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border: var(--border);
|
||||
border-radius: var(--border-radius, 1rem);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.section-row {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.section-element {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-element label {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.section-element > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||