First commit
24
.dockerignore
Normal file
@ -0,0 +1,24 @@
|
||||
node_modules
|
||||
Dockerfile
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
15
.prettierrc
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM node:22-slim as builder
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . ./
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-slim
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app/build ./
|
||||
EXPOSE 3000
|
||||
CMD ["node", "index.js"]
|
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.
|
34
eslint.config.js
Normal file
@ -0,0 +1,34 @@
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import js from '@eslint/js';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript-eslint';
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default ts.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
3715
package-lock.json
generated
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "fjla-home",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.3",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
"globals": "^15.0.0",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.2.6",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/mongodb-adapter": "^3.7.4",
|
||||
"@auth/sveltekit": "^1.7.4",
|
||||
"mongodb": "^6.12.0"
|
||||
}
|
||||
}
|
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 {};
|
12
src/app.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents; width: 100%">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
14
src/auth.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import client from "$lib/db"
|
||||
import { MongoDBAdapter } from "@auth/mongodb-adapter"
|
||||
import { SvelteKitAuth } from "@auth/sveltekit"
|
||||
import Keycloak from "@auth/sveltekit/providers/keycloak"
|
||||
|
||||
export const { handle, signIn, signOut } = SvelteKitAuth({
|
||||
providers: [Keycloak],
|
||||
trustHost: true,
|
||||
adapter: MongoDBAdapter(client),
|
||||
session: {
|
||||
maxAge: 14400, // Limit session length to four hours
|
||||
updateAge: 1800, // Update token every 30 minutes
|
||||
},
|
||||
})
|
1
src/hooks.server.ts
Normal file
@ -0,0 +1 @@
|
||||
export { handle } from "./auth"
|
68
src/lib/HeaderBar.svelte
Normal file
@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
</script>
|
||||
|
||||
<div id="header-bar">
|
||||
<div id="logo-box">
|
||||
<a href="/">
|
||||
<img class="logo-img" src="/logo/logo-colour.svg" alt="FJLA Logo" width="50" height="50">
|
||||
<!--<img class="logo-img" id="logo-black" src="/logo/logo-black.svg" alt="FJLA Logo" width="50" height="50">
|
||||
--> </a>
|
||||
</div>
|
||||
{#if $page.data.session}
|
||||
<a class="account-link" href="/logout">Sign out</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div id="spacer"></div>
|
||||
|
||||
<style>
|
||||
#header-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: darkslategrey;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
#logo-box {
|
||||
padding: 5px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.account-link {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
height: 60px;
|
||||
width: 140px;
|
||||
color: white;
|
||||
background-color: darkslategrey;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
font-weight: 900;
|
||||
font-size: larger;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: 0px;
|
||||
|
||||
/* Flexbox styles for centering */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.account-link:hover {
|
||||
background-color: rgb(54, 90, 90);
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
</style>
|
27
src/lib/app-cards/AppCardArray.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import MasterAppCard from "./MasterAppCard.svelte";
|
||||
</script>
|
||||
|
||||
<div id="card-container">
|
||||
<MasterAppCard appName={"My Account"} appDesc={"Manage your password and passkeys"} iconName={"myaccount"} sso={true} appUrl={"https://sso.fjla.uk/realms/FJLA.net/account"} />
|
||||
<MasterAppCard appName={"Nextcloud"} appDesc={"Files, Email, Calendar, Chat"} iconName={"nextcloud"} sso={false} appUrl={"https://cloud.fjla.uk"} bgColor={"#0082c9"} />
|
||||
<MasterAppCard appName={"Portainer"} appDesc={"Manage containerised workloads in Docker"} iconName={"portainer"} sso={true} appUrl={"https://swarm_nodes.fjla.net:9443"} nointernet={true} bgColor={"#3BBCED"} />
|
||||
<MasterAppCard appName={"SpeedyF"} appDesc={"Online Compressor for PDF Files"} iconName={"speedyf"} sso={false} appUrl={"https://speedyf.fjla.uk"} bgColor={"#00001a"} />
|
||||
<MasterAppCard appName={"Jellyfin"} appDesc={"Stream films and TV, watch Live TV"} iconName={"jellyfin"} sso={false} appUrl={"http://jf.fjla.net:8096"} nointernet={true} bgColor={"#AA5CC3"} />
|
||||
<MasterAppCard appName={"Proxmox"} appDesc={"Manage virtual machines"} iconName={"proxmoxve"} sso={true} appUrl={"https://pve0124.fjla.net:8006"} nointernet={true} bgColor={"#e57000"} />
|
||||
<MasterAppCard appName={"Home Assistant"} appDesc={"Smart Home Management"} iconName={"homeassistant"} sso={false} appUrl={"https://ha.fjla.uk"} bgColor={"#18BCF2"} />
|
||||
<MasterAppCard appName={"Gitea"} appDesc={"Code & Package Repo"} iconName={"gitea"} sso={true} appUrl={"https://git.fjla.uk"} bgColor={"#609926"} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#card-container {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 1px;
|
||||
padding: 0px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
0
src/lib/app-cards/CardSettings.ts
Normal file
79
src/lib/app-cards/MasterAppCard.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
export let appName: string;
|
||||
export let appDesc: string;
|
||||
export let iconName: string;
|
||||
export let sso: boolean;
|
||||
export let nointernet: boolean = false;
|
||||
export let appUrl: string = "#";
|
||||
export let bgColor: string = "rgb(134, 134, 134)"
|
||||
</script>
|
||||
|
||||
<div id="app-card" style="background-color:{bgColor}">
|
||||
<a class="app-link" href={appUrl}>
|
||||
<img src="/icons/{iconName}.svg" alt="{iconName}">
|
||||
<div id="card-text">
|
||||
<header id="app-name">{appName}</header>
|
||||
<p id="app-desc">{appDesc}</p>
|
||||
</div>
|
||||
<div id="sign-on-type">
|
||||
{#if sso}
|
||||
<img src="/logo/logo-black.svg" alt="Single Sign-on" width=25 height=25>
|
||||
{:else}
|
||||
<img src="/icons/not-sso.svg" alt="Single Sign-on not supported" width=25 height=25>
|
||||
{/if}
|
||||
{#if nointernet}
|
||||
<img src="/icons/nointernet.svg" alt="Not available when not connected to FJLA WiFi" width=25 height=25>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
#app-card {
|
||||
width: calc(25% - 2px);
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* Responsive styles for smaller screens */
|
||||
@media (max-width: 875px) {
|
||||
#app-card {
|
||||
width: calc(50% - 2px); /* 2 cards per row on smaller screens */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 676px) {
|
||||
#app-card {
|
||||
width: calc(100% - 2px); /* 1 card per row on very small screens */
|
||||
}
|
||||
}
|
||||
|
||||
#app-card:hover {
|
||||
opacity: 75%;
|
||||
}
|
||||
|
||||
#app-name {
|
||||
font-weight: 600;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
#app-desc {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.app-link{
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
11
src/lib/buttons/Login.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { SignIn } from "@auth/sveltekit/components";
|
||||
|
||||
</script>
|
||||
|
||||
<SignIn provider="keycloak" redirectTo="/">
|
||||
<div slot="submitButton" class="buttonPrimary">Login</div>
|
||||
</SignIn>
|
||||
|
||||
<style>
|
||||
</style>
|
35
src/lib/db.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { MongoClient, ServerApiVersion } from "mongodb";
|
||||
|
||||
if (!process.env.MONGODB_URI) {
|
||||
console.log(process.env.MONGODB_URI)
|
||||
console.log("MONGODB_URI Not valid, auth will not work");
|
||||
}
|
||||
|
||||
const uri = process.env.MONGODB_URI || "mongodb://localhost:27017";
|
||||
const options = {
|
||||
serverApi: {
|
||||
version: ServerApiVersion.v1,
|
||||
strict: true,
|
||||
deprecationErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
let client: MongoClient
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
const globalWithMongo = global as typeof globalThis & {
|
||||
_mongoClient?: MongoClient
|
||||
}
|
||||
|
||||
if (!globalWithMongo._mongoClient) {
|
||||
globalWithMongo._mongoClient = new MongoClient(uri, options)
|
||||
}
|
||||
client = globalWithMongo._mongoClient
|
||||
} else {
|
||||
// In production mode, it's best to not use a global variable.
|
||||
client = new MongoClient(uri, options)
|
||||
}
|
||||
|
||||
// Export a module-scoped MongoClient. By doing this in a
|
||||
// separate module, the client can be shared across functions.
|
||||
export default client
|
1
src/lib/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
48
src/lib/notLoggedIn.svelte
Normal file
@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import Login from "./buttons/Login.svelte";
|
||||
</script>
|
||||
|
||||
<div id="content-container">
|
||||
<img id="large-logo" src="/logo/logo-colour.svg" width=125 height=125 alt="Logo">
|
||||
|
||||
<div id="bounding-box">
|
||||
<header>You are not signed in</header>
|
||||
<Login />
|
||||
|
||||
<p>When you login for the first time, you will have to verify your email address and register a passkey to enable your account, <a href="passkey">click here</a> for help.</p>
|
||||
<p><a href="https://sso.fjla.uk/realms/FJLA.net/login-actions/reset-credentials" target="_blank">Forgot your password?</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
#content-container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 10 auto;
|
||||
padding: 25px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#large-logo {
|
||||
margin: 0 auto;
|
||||
}
|
||||
#bounding-box {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
background-color: lightgrey;
|
||||
width: 75%;
|
||||
min-width: 200px;
|
||||
max-width: 800px;
|
||||
border-radius: 25px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
header {
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
font-size: large;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
9
src/routes/+layout.server.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type { LayoutServerLoad } from "./$types"
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
const session = await event.locals.auth()
|
||||
|
||||
return {
|
||||
session,
|
||||
}
|
||||
}
|
4
src/routes/+layout.svelte
Normal file
@ -0,0 +1,4 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<slot />
|
8
src/routes/+page.server.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const session = await event.locals.auth();
|
||||
if (!session?.user) throw redirect(303, '/login');
|
||||
return {};
|
||||
};
|
37
src/routes/+page.svelte
Normal file
@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import NotLoggedIn from "$lib/notLoggedIn.svelte";
|
||||
import AppCardArray from "$lib/app-cards/AppCardArray.svelte";
|
||||
import HeaderBar from "$lib/HeaderBar.svelte";
|
||||
</script>
|
||||
{#if page.data.session}
|
||||
<HeaderBar />
|
||||
<h1>{page?.data?.session?.user?.name}'s Apps</h1>
|
||||
<div id="icon-key">
|
||||
<img src="/logo/logo-black.svg" alt="FJLA Logo" width=20 height=20> Single Sign On
|
||||
<br>
|
||||
<img src="/icons/not-sso.svg" alt="Separate Password Icon" width=20 height=20 style="background-color:black;border-radius:5px;"> Uses a separate Login
|
||||
<br>
|
||||
<img src="/icons/nointernet.svg" alt="Only on FJLA WiFi" width=20 height=20 style="background-color:black;border-radius:5px;"> Only available on FJLA WiFi
|
||||
</div>
|
||||
<AppCardArray />
|
||||
{:else}
|
||||
<NotLoggedIn />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
text-align: center;
|
||||
margin: 5px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
#icon-key {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
45
src/routes/backchannel-logout/+server.ts
Normal file
@ -0,0 +1,45 @@
|
||||
const handler = async ({ request }: { request: Request }) => {
|
||||
// Log the HTTP method
|
||||
console.log('HTTP Method:', request.method);
|
||||
|
||||
// Log the full URL
|
||||
console.log('URL:', request.url);
|
||||
|
||||
// Log request headers
|
||||
console.log('Headers:', Object.fromEntries(request.headers.entries()));
|
||||
|
||||
// Log the body, depending on its content type
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
const jsonBody = await request.json();
|
||||
console.log('JSON Body:', JSON.stringify(jsonBody, null, 2));
|
||||
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
||||
const formData = await request.formData();
|
||||
const formObject: Record<string, string> = {};
|
||||
formData.forEach((value, key) => {
|
||||
formObject[key] = value.toString();
|
||||
});
|
||||
console.log('Form Data:', formObject);
|
||||
} else if (contentType.includes('multipart/form-data')) {
|
||||
const formData = await request.formData();
|
||||
const formObject: Record<string, string> = {};
|
||||
formData.forEach((value, key) => {
|
||||
formObject[key] = value.toString();
|
||||
});
|
||||
console.log('Multipart Form Data:', formObject);
|
||||
} else {
|
||||
const textBody = await request.text();
|
||||
console.log('Text Body:', textBody);
|
||||
}
|
||||
|
||||
return new Response('Request logged!', { status: 200 });
|
||||
};
|
||||
|
||||
// Bind the handler to all HTTP methods
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
export const PUT = handler;
|
||||
export const DELETE = handler;
|
||||
export const PATCH = handler;
|
||||
export const OPTIONS = handler;
|
21
src/routes/change/+page.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { signOut } from "@auth/sveltekit/client";
|
||||
|
||||
const changePwLink: string = "https://sso.fjla.uk/realms/FJLA.net/account/account-security/signing-in"
|
||||
|
||||
onMount(async() => {
|
||||
await signOut();
|
||||
window.location.href = changePwLink;
|
||||
})
|
||||
</script>
|
||||
|
||||
<p>Redirecting, please wait...</p>
|
||||
<p>If the page does not reload <a href={changePwLink}>click here</a>.</p>
|
||||
|
||||
<style>
|
||||
p {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
6
src/routes/debug/+page.svelte
Normal file
@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import NotLoggedIn from "$lib/notLoggedIn.svelte";
|
||||
</script>
|
||||
|
||||
<NotLoggedIn />
|
21
src/routes/forgot/+page.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { signOut } from "@auth/sveltekit/client";
|
||||
|
||||
const changePwLink: string = "https://sso.fjla.uk/realms/FJLA.net/login-actions/reset-credentials"
|
||||
|
||||
onMount(async() => {
|
||||
await signOut();
|
||||
window.location.href = changePwLink;
|
||||
})
|
||||
</script>
|
||||
|
||||
<p>Redirecting, please wait...</p>
|
||||
<p>If the page does not reload <a href={changePwLink}>click here</a>.</p>
|
||||
|
||||
<style>
|
||||
p {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
8
src/routes/login/+page.server.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const session = await event.locals.auth();
|
||||
if (session?.user) throw redirect(303, '/');
|
||||
return {};
|
||||
};
|
5
src/routes/login/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import NotLoggedIn from "$lib/notLoggedIn.svelte";
|
||||
</script>
|
||||
|
||||
<NotLoggedIn />
|
26
src/routes/logout/+page.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { signOut } from "@auth/sveltekit/client";
|
||||
|
||||
const keycloakUrl = "https://sso.fjla.uk";
|
||||
const keycloakRealm = "FJLA.net";
|
||||
const postLogoutRedirect = "https://fjla.uk/";
|
||||
const clientId = "fjla-home"
|
||||
|
||||
const globalLogoutUrl = `${keycloakUrl}/realms/${keycloakRealm}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirect)}&client_id=${clientId}`;
|
||||
|
||||
onMount(async() => {
|
||||
await signOut();
|
||||
window.location.href = globalLogoutUrl;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<p>Signing out...</p>
|
||||
|
||||
<style>
|
||||
p {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
52
src/routes/passkey/+page.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
|
||||
import HeaderBar from "$lib/HeaderBar.svelte";
|
||||
|
||||
</script>
|
||||
<HeaderBar />
|
||||
|
||||
<header>Passkey Help</header>
|
||||
|
||||
<p>FJLA requires a passkey registered to your account.</p>
|
||||
|
||||
<p>A passkey increases your password by supplementing it, or replacing it completely.</p>
|
||||
|
||||
<p class="subhead">What is a Passkey?</p>
|
||||
|
||||
<p>A passkey is a digital credential that allows users to authenticate and sign into their accounts without using a traditional password. It relies on biometric data such as a fingerprint, face scan, or PIN to verify the user’s identity, ensuring that the sign-in process is both secure and convenient. Passkeys are designed to be resistant to phishing attacks and other forms of cybercrime, as they cannot be stolen or shared like passwords. They are supported by various platforms and can be used across different devices to simplify account registration and improve the overall user experience.</p>
|
||||
|
||||
<p class="subhead">How do I get a Passkey?</p>
|
||||
|
||||
<p>Most mobile devices and many newer computers can act as a passkey themselves. Alternatively, you can purchase a USB passkey - <a href="https://www.yubico.com/gb/product/security-key-nfc-by-yubico-black/" target="_blank">example from Yubico.</a></p>
|
||||
|
||||
<p class="subhead">What's the point?</p>
|
||||
|
||||
<p>By configuring your accounts with a Passkey, security is quickly and easily improved massively. You can use passkeys on many websites and services - if you configure passwordless login, you don't even need to remember a password.</p>
|
||||
|
||||
<p class="subhead">How do I configure passwordless login?</p>
|
||||
|
||||
<p>Login to your FJLA Account on the homepage, then click on My Account, Click on the menu, then 'Account Security', then 'Signing In'. At the bottom of the page, add your passkey in the Passwordless section.</p>
|
||||
<p>If you don't configure passwordless, you will need your password and your passkey to login. Some passkeys do not support passwordless login.</p>
|
||||
|
||||
<style>
|
||||
header {
|
||||
text-align: center;
|
||||
font-size: larger;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
font-weight: 600;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.subhead {
|
||||
margin-top: 40px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
3
src/routes/signin/+page.server.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { signIn } from "../../auth";
|
||||
import type { Actions } from "./$types";
|
||||
export const actions: Actions = { default: signIn }
|
BIN
static/favicon.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
1
static/icons/gitea.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#fff" fill-rule="evenodd" d="M15.46 3.206s.14-.003.245.102l.01.01c.064.06.258.244.285 1.074c0 2.902-1.405 5.882-1.405 5.882a9 9 0 0 1-.359.713c-.458.802-.786 1.153-.786 1.153s-.318.379-.675.595c-.415.265-.72.263-.72.263L7.247 13c-.636-.079-1.29-.736-1.927-1.578c-.47-.677-.779-1.413-.779-1.413s-2.51.034-3.675-1.394C.235 7.895.103 7.067.06 6.769q0-.012-.004-.029c-.05-.324-.285-1.873.821-2.86c.517-.496 1.148-.638 1.37-.684c.371-.081.667-.06.903-.044l.09.006c.391.035 3.99.216 3.99.216s1.532.066 2.27.056c0 0 .003 1.853.003 2.78q.105.048.211.1l.212.1V3.427q.494-.005.996-.017h.011c1.545-.036 4.528-.204 4.528-.204ZM2.113 8.026s.28.26.94.477c.43.152 1.094.231 1.094.231S3.699 7.5 3.516 6.757c-.22-.886-.4-2.398-.4-2.398s-.438-.015-.789.079c-.766.19-.98.763-.98.763s-.384.688.036 1.813c.244.672.73 1.013.73 1.013Zm8.084 3.607c.344-.023.499-.392.499-.392s1.24-2.486 1.4-2.878a.7.7 0 0 0 .046-.438c-.07-.267-.39-.412-.39-.412l-1.926-.935l-.165.339l-.18.369a.46.46 0 0 1 .128.341s.433.186.743.387c0 0 .257.135.32.425c.075.273-.04.488-.066.539l-.002.003s-.216.51-.343.774l-.004.007q-.07.144-.139.28a.454.454 0 1 1-.32-.15s.41-.84.468-1.033c0 0 .096-.24.048-.38a.47.47 0 0 0-.19-.188a6 6 0 0 0-.678-.34s-.076.068-.18.09a.5.5 0 0 1-.158.014l-.611 1.25a.46.46 0 0 1 .046.587a.46.46 0 0 1-.578.138a.46.46 0 0 1-.232-.51a.46.46 0 0 1 .44-.35L8.8 7.886a.457.457 0 0 1 .361-.744l.185-.375l.167-.341l-.579-.281s-.251-.125-.458-.072a.6.6 0 0 0-.114.039c-.189.084-.31.33-.31.33L6.668 9.293s-.124.254-.068.46c.048.252.325.397.325.397l2.874 1.4l.135.054s.114.04.262.03Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
static/icons/gotify.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M32.632 35.406c-1.59 2.423-3.29 5.035-7.064 6.175c-3.782 1.13-9.758.967-13.407-.719c-3.658-1.695-4.99-4.922-4.31-7.422c.67-2.508 3.343-4.3 4.55-6.224c1.197-1.935.929-4.003.613-5.918a28 28 0 0 1-.613-5.095a10.3 10.3 0 0 1 1.029-3.737"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M14.555 12.612c-6.943-.48-1.436-6.704.958-1.916"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M15.181 10.13c4.55-5.266 21.4-6.74 23.076 6.666"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M19.822 7.273c.185-1.999 1.217-2.154 2.616-.672m15.819 10.196a1.214 1.214 0 0 1 1.312 1.082a1.345 1.345 0 0 1-2.633 0a1.217 1.217 0 0 1 1.321-1.082"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M37.06 18.358c-5.507 3.954 5.381 4.123 2.509-.386M28.68 11.175a5.746 5.746 0 1 1-5.745 5.746a5.744 5.744 0 0 1 5.746-5.746"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M30.596 15.724a1.323 1.323 0 1 1-1.197 1.312a1.257 1.257 0 0 1 1.197-1.312m7.901 5.424v1.785M36.29 11.294c2.227-.738 4.476 2.871 3.03 5.951m-12.392 8.209l13.14-2.864a1.4 1.4 0 0 1 1.664 1.067l.001.006l1.734 7.948a1.4 1.4 0 0 1-1.063 1.666l-13.149 2.864a1.4 1.4 0 0 1-1.665-1.066l-.001-.006l-1.733-7.949a1.4 1.4 0 0 1 1.066-1.665ZM8.884 31.378c-5.77-2.452-1.072-5.023.829-1.03M4.5 37.99q3.112 2.872 5.028-.719m21.454.497c1.676-.389 2.225.73 1.53 3.334"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M23.174 26.498q1.197 2.288 3.83.134"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="m26.047 26.976l9.098 3.965l6.294-7.882M28.479 36.09l4.74-5.988m3.341-.932l6.691 3.508"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
static/icons/homeassistant.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M22.939 10.627L13.061.749a1.505 1.505 0 0 0-2.121 0l-9.879 9.878C.478 11.21 0 12.363 0 13.187v9c0 .826.675 1.5 1.5 1.5h9.227l-4.063-4.062a2 2 0 0 1-.664.113c-1.13 0-2.05-.92-2.05-2.05s.92-2.05 2.05-2.05s2.05.92 2.05 2.05c0 .233-.041.456-.113.665l3.163 3.163V9.928a2.05 2.05 0 0 1-1.15-1.84c0-1.13.92-2.05 2.05-2.05s2.05.92 2.05 2.05a2.05 2.05 0 0 1-1.15 1.84v8.127l3.146-3.146A2.05 2.05 0 0 1 18 12.239c1.13 0 2.05.92 2.05 2.05s-.92 2.05-2.05 2.05a2 2 0 0 1-.709-.13L12.9 20.602v3.088h9.6c.825 0 1.5-.675 1.5-1.5v-9c0-.825-.477-1.977-1.061-2.561z"/></svg>
|
After Width: | Height: | Size: 659 B |
1
static/icons/jellyfin.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12 .002C8.826.002-1.398 18.537.16 21.666c1.56 3.129 22.14 3.094 23.682 0S15.177 0 12 0zm7.76 18.949c-1.008 2.028-14.493 2.05-15.514 0C3.224 16.9 9.92 4.755 12.003 4.755c2.081 0 8.77 12.166 7.759 14.196zM12 9.198c-1.054 0-4.446 6.15-3.93 7.189c.518 1.04 7.348 1.027 7.86 0c.511-1.027-2.874-7.19-3.93-7.19z"/></svg>
|
After Width: | Height: | Size: 418 B |
1
static/icons/myaccount.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#fff" stroke-dasharray="28" stroke-dashoffset="28" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M4 21v-1c0 -3.31 2.69 -6 6 -6h4c3.31 0 6 2.69 6 6v1"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.4s" values="28;0"/></path><path d="M12 11c-2.21 0 -4 -1.79 -4 -4c0 -2.21 1.79 -4 4 -4c2.21 0 4 1.79 4 4c0 2.21 -1.79 4 -4 4Z"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.4s" values="28;0"/></path></g></svg>
|
After Width: | Height: | Size: 586 B |
1
static/icons/nextcloud.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12.018 6.537c-2.5 0-4.6 1.712-5.241 4.015c-.56-1.232-1.793-2.105-3.225-2.105A3.57 3.57 0 0 0 0 12a3.57 3.57 0 0 0 3.552 3.553c1.432 0 2.664-.874 3.224-2.106c.641 2.304 2.742 4.016 5.242 4.016c2.487 0 4.576-1.693 5.231-3.977c.569 1.21 1.783 2.067 3.198 2.067A3.57 3.57 0 0 0 24 12a3.57 3.57 0 0 0-3.553-3.553c-1.416 0-2.63.858-3.199 2.067c-.654-2.284-2.743-3.978-5.23-3.977m0 2.085c1.878 0 3.378 1.5 3.378 3.378s-1.5 3.378-3.378 3.378A3.36 3.36 0 0 1 8.641 12c0-1.878 1.5-3.378 3.377-3.378m-8.466 1.91c.822 0 1.467.645 1.467 1.468s-.644 1.467-1.467 1.468A1.45 1.45 0 0 1 2.085 12a1.45 1.45 0 0 1 1.467-1.467zm16.895 0c.823 0 1.468.645 1.468 1.468s-.645 1.468-1.468 1.468A1.45 1.45 0 0 1 18.98 12a1.45 1.45 0 0 1 1.467-1.467z"/></svg>
|
After Width: | Height: | Size: 837 B |
1
static/icons/nointernet.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="#fff"><path stroke="#fff" d="M5 5a9.85 9.85 0 0 0-3 7.083C2 17.56 6.477 22 12 22a10 10 0 0 0 7-2.835"/><path stroke="#fff" d="M15.5 16c-.617 3.532-1.86 6-3.5 6c-2.21 0-4-4.477-4-10c0-1.231.089-2.41.252-3.5M2 12h10"/><path fill="#fff" d="M7.162 2.398a.75.75 0 0 0 .676 1.339zm13.1 13.764a.75.75 0 0 0 1.34.676zM16 12h-.75c0 .414.336.75.75.75zM8.303 4.726a.75.75 0 0 0 1.396.548zM12 2.75A9.25 9.25 0 0 1 21.25 12h1.5c0-5.937-4.813-10.75-10.75-10.75zm-4.162.987A9.2 9.2 0 0 1 12 2.75v-1.5a10.7 10.7 0 0 0-4.838 1.148zM21.25 12a9.2 9.2 0 0 1-.987 4.162l1.339.676A10.7 10.7 0 0 0 22.75 12zM12 2.75c.28 0 .622.139 1.012.551c.392.414.78 1.054 1.12 1.906c.68 1.701 1.118 4.103 1.118 6.793h1.5c0-2.832-.458-5.431-1.225-7.35c-.383-.957-.858-1.782-1.423-2.38c-.567-.599-1.277-1.02-2.102-1.02zM9.7 5.274c.335-.855.755-1.51 1.183-1.938c.433-.432.822-.586 1.117-.586v-1.5c-.824 0-1.572.42-2.177 1.025c-.61.61-1.13 1.455-1.52 2.451zM16 12.75h6v-1.5h-6z"/><path stroke="#fff" d="m2 2l20 20"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
static/icons/not-sso.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M2 19v-2h20v2zm1.15-6.05l-1.3-.75l.85-1.5H1V9.2h1.7l-.85-1.45L3.15 7L4 8.45L4.85 7l1.3.75L5.3 9.2H7v1.5H5.3l.85 1.5l-1.3.75l-.85-1.5zm8 0l-1.3-.75l.85-1.5H9V9.2h1.7l-.85-1.45l1.3-.75l.85 1.45l.85-1.45l1.3.75l-.85 1.45H15v1.5h-1.7l.85 1.5l-1.3.75l-.85-1.5zm8 0l-1.3-.75l.85-1.5H17V9.2h1.7l-.85-1.45l1.3-.75l.85 1.45l.85-1.45l1.3.75l-.85 1.45H23v1.5h-1.7l.85 1.5l-1.3.75l-.85-1.5z"/></svg>
|
After Width: | Height: | Size: 491 B |
1
static/icons/omada.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12.766 7.75c-1.515 0-2.9.672-3.785 1.907l.658.675c.71-.99 1.908-1.625 3.127-1.625c1.22 0 2.421.634 3.131 1.625l.656-.674c-.885-1.235-2.271-1.907-3.787-1.907m-.006 2.192c-.966 0-1.842.4-2.48 1.194l.665.684c.461-.573 1.127-.921 1.815-.921s1.354.363 1.815.937l.674-.692a3.13 3.13 0 0 0-2.489-1.202m5.958.993v1.736a2 2 0 0 0-1.276-.466c-1.1 0-1.993.897-1.993 2s.894 2 1.993 2c.485 0 .93-.175 1.276-.465v.304h.718v-5.109Zm-12.524 1.27c-.883 0-1.6.72-1.6 1.606v2.234h.717V13.81c0-.488.396-.885.882-.885c.487 0 .884.397.884.885v2.234h.717V13.81c0-.488.396-.885.883-.885s.884.397.884.885v2.234h.717V13.81c0-.885-.718-1.606-1.6-1.606a1.59 1.59 0 0 0-1.242.595a1.6 1.6 0 0 0-1.242-.595m6.668 0c-1.1 0-1.994.897-1.994 2s.894 2 1.994 2c.465 0 .916-.166 1.275-.467v.306h.718v-1.84a2 2 0 0 0-1.993-1.999m9.145 0a1.999 1.999 0 0 0 0 4c.465 0 .916-.166 1.275-.467v.306H24v-1.84a2 2 0 0 0-1.993-1.999m-20.012.045a2 2 0 0 0 0 4a2 2 0 0 0 0-4m10.867.676a1.28 1.28 0 0 1 0 2.558a1.278 1.278 0 0 1 0-2.559m4.58 0a1.28 1.28 0 0 1 0 2.558a1.279 1.279 0 0 1 0-2.559m4.565 0a1.279 1.279 0 0 1 0 2.558a1.28 1.28 0 0 1 0-2.559m-19.999.058a1.279 1.279 0 0 1 0 2.558a1.279 1.279 0 0 1 0-2.558"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
static/icons/portainer.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12.504 0v1.023l-.01-.015l-6.106 3.526H3.417v.751h5.359v3.638h1.942V5.284h1.786V15.7c.027 0 .54-.01.751.091V5.285h.531v10.608c.293.147.55.312.751.54V5.286h6.046v-.75h-1.267l-6.061-3.5V0zm0 1.87v2.664H7.889zm.751.031l4.56 2.633h-4.56zM9.142 5.285h1.21v1.686h-1.21zm-4.736 2.73v1.951h1.942v-1.95zm2.19 0v1.951h1.941v-1.95zm-2.19 2.171v1.951h1.942v-1.95zm2.19 0v1.951h1.941v-1.95zm2.18 0v1.951h1.942v-1.95zM4.36 12.43a3.73 3.73 0 0 0-.494 1.851c0 1.227.604 2.308 1.52 2.986c.239-.064.477-.1.724-.11c.1 0 .165.01.266.019c.284-1.191 1.383-1.988 2.665-1.988c.724 0 1.438.201 1.924.668c.229-.476.302-1.007.302-1.575c0-.65-.165-1.292-.494-1.85zm4.828 3.16c-1.21 0-2.226.844-2.492 1.97a1 1 0 0 0-.275-.009a2.56 2.56 0 0 0-2.564 2.556a2.565 2.565 0 0 0 3.096 2.5A2.58 2.58 0 0 0 9.233 24c.862 0 1.622-.43 2.09-1.081a2.557 2.557 0 0 0 4.186-1.97c0-.567-.193-1.099-.504-1.52a2.557 2.557 0 0 0-3.866-2.94a2.57 2.57 0 0 0-1.951-.898z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
static/icons/proxmoxve.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M4.928 1.825c-1.09.553-1.09.64-.07 1.78c5.655 6.295 7.004 7.782 7.107 7.782c.139.017 7.971-8.542 8.058-8.801c.034-.07-.208-.312-.519-.536c-.415-.312-.864-.433-1.712-.467c-1.59-.104-2.144.242-4.115 2.455c-.899 1.003-1.66 1.833-1.66 1.833c-.017 0-.76-.813-1.642-1.798S8.473 2.1 8.127 1.91c-.796-.45-2.421-.484-3.2-.086zM1.297 4.367C.45 4.695 0 5.007 0 5.248c0 .121 1.331 1.678 2.94 3.459c1.625 1.78 2.939 3.268 2.939 3.302s-1.331 1.522-2.94 3.303C1.314 17.11.017 18.683.035 18.822c.086.467 1.504 1.055 2.541 1.055c1.678-.018 2.058-.312 5.603-4.202c1.78-1.954 3.233-3.614 3.233-3.666c0-.069-1.435-1.694-3.199-3.63c-2.3-2.508-3.423-3.632-3.96-3.874c-.812-.398-2.126-.467-2.956-.138m18.467.12c-.502.26-1.764 1.505-3.943 3.891c-1.763 1.937-3.199 3.562-3.199 3.631c0 .07 1.453 1.712 3.234 3.666c3.544 3.89 3.925 4.184 5.602 4.202c1.038 0 2.455-.588 2.542-1.055c.017-.156-1.28-1.712-2.905-3.493c-1.608-1.78-2.94-3.285-2.94-3.32s1.332-1.539 2.94-3.32C22.72 6.91 24.017 5.352 24 5.214c-.087-.45-1.366-.968-2.473-1.038c-.795-.034-1.21.035-1.763.312zM7.954 16.973c-2.144 2.369-3.908 4.374-3.943 4.46c-.034.07.208.312.52.537c.414.311.864.432 1.711.467c1.574.103 2.161-.26 4.15-2.508c.864-.968 1.608-1.78 1.625-1.78s.761.812 1.643 1.798c2.023 2.248 2.559 2.576 4.132 2.49c.848-.035 1.297-.156 1.712-.467c.311-.225.553-.467.519-.536c-.087-.26-7.92-8.819-8.058-8.801c-.069 0-1.867 1.954-4.011 4.34"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
static/icons/speedyf.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path fill="#fff" fill-rule="evenodd" d="M5.8 14H5v1h.8c.3 0 .5-.2.5-.5s-.2-.5-.5-.5M11 2H3v16h13V7zM7.2 14.6c0 .8-.6 1.4-1.4 1.4H5v1H4v-4h1.8c.8 0 1.4.6 1.4 1.4zm4.1.5c0 1-.8 1.9-1.9 1.9H8v-4h1.4c1 0 1.9.8 1.9 1.9zM15 14h-2v1h1.5v1H13v1h-1v-4h3zm0-2H4V3h7v4h4zm-5.6 2H9v2h.4c.6 0 1-.4 1-1s-.5-1-1-1" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 411 B |
1
static/icons/traccar.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M6.001 1.61C.262 4.923-1.704 12.26 1.61 17.999s10.65 7.705 16.389 4.392S25.704 11.74 22.39 6S11.74-1.704 6 1.61m.706 1.222C11.77-.091 18.245 1.644 21.168 6.707s1.189 11.538-3.874 14.461s-11.538 1.189-14.462-3.874S1.644 5.756 6.707 2.832m3.914 14.315a3.768 3.768 0 1 1-3.768-6.526l1.884 3.263Zm5.725-11.395L15.17 7.247c.665.511 1.28 1.156 1.725 1.927s.696 1.626.807 2.458l1.882-.272a8.4 8.4 0 0 0-3.238-5.608m-2.272 3.011l-1.176 1.495c.274.216.548.48.734.8s.276.69.325 1.036l1.883-.272a4.6 4.6 0 0 0-.577-1.706a4.6 4.6 0 0 0-1.189-1.353m-3.832 3.708a.942.942 0 1 0 1.884 0a.942.942 0 1 0-1.884 0"/></svg>
|
After Width: | Height: | Size: 707 B |
10
static/logo/logo-black.svg
Normal file
After Width: | Height: | Size: 33 KiB |
10
static/logo/logo-colour.svg
Normal file
After Width: | Height: | Size: 33 KiB |
27
static/logo/logo-white.svg
Normal file
After Width: | Height: | Size: 34 KiB |
18
svelte.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://svelte.dev/docs/kit/integrations
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
6
vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|