mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2025-12-11 13:26:16 +00:00
update deployment
This commit is contained in:
40
.github/workflows/build.yml
vendored
40
.github/workflows/build.yml
vendored
@@ -1,13 +1,9 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
CI:
|
||||
build:
|
||||
name: 🔨🚀 Build and deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -25,10 +21,10 @@ jobs:
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
- name: 🐉 Use Node.js 18.16.x
|
||||
- name: 🐉 Use Node.js 22.4.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.16.x
|
||||
node-version: 22.4.x
|
||||
cache: "pnpm"
|
||||
- name: ⏬ Install Node dependencies
|
||||
run: pnpm install
|
||||
@@ -38,17 +34,17 @@ jobs:
|
||||
- name: 🔨 Build site
|
||||
run: pnpm build
|
||||
|
||||
- name: 📦 Upload build artifacts
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- name: Disable jekyll
|
||||
run: touch build/.nojekyll
|
||||
- name: Custom domain
|
||||
run: echo 'manager.charachorder.com' > build/CNAME
|
||||
- run: git config user.name github-actions
|
||||
- run: git config user.email github-actions@github.com
|
||||
- run: git --work-tree build add --all
|
||||
- run: git commit -m "Automatic Deploy action run by github-actions"
|
||||
- run: git push origin HEAD:gh-pages --force
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
install -m 600 -D /dev/null ~/.ssh/id_rsa
|
||||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_rsa
|
||||
echo "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
|
||||
|
||||
- name: Publish Stable
|
||||
if: ${{ github.ref == 'refs/heads/v*' }}
|
||||
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/www/
|
||||
|
||||
- name: Publish Branch
|
||||
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/ref/${GITHUB_REF##*/}
|
||||
- name: Publish Commit
|
||||
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/ref/${{ github.sha }}
|
||||
|
||||
@@ -18,6 +18,8 @@ const config = {
|
||||
"update",
|
||||
"offline_pin",
|
||||
"warning",
|
||||
"dangerous",
|
||||
"check",
|
||||
"cable",
|
||||
"person",
|
||||
"sync",
|
||||
|
||||
1
src/env.d.ts
vendored
1
src/env.d.ts
vendored
@@ -15,6 +15,7 @@ interface ImportMetaEnv {
|
||||
readonly VITE_LATEST_FIRMWARE: string;
|
||||
readonly VITE_STORE_URL: string;
|
||||
readonly VITE_MATRIX_URL: string;
|
||||
readonly VITE_FIRMWARE_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
16
src/routes/(app)/ota-update/+layout.svelte
Normal file
16
src/routes/(app)/ota-update/+layout.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<h1><a href="/ota-update/">Firmware Update</a></h1>
|
||||
|
||||
{@render children()}
|
||||
|
||||
<style lang="scss">
|
||||
h1 {
|
||||
margin-block: 1em;
|
||||
padding: 0;
|
||||
font-size: 3em;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
2
src/routes/(app)/ota-update/+layout.ts
Normal file
2
src/routes/(app)/ota-update/+layout.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const prerender = false;
|
||||
export const ssr = false;
|
||||
@@ -1,15 +1,91 @@
|
||||
<script lang="ts">
|
||||
import { serialPort } from "$lib/serial/connection";
|
||||
import { slide } from "svelte/transition";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let files: FileList | null = $state(null);
|
||||
|
||||
$effect(() => {
|
||||
const file = files?.[0];
|
||||
if (file && $serialPort) {
|
||||
|
||||
$serialPort.updateFirmware(file);
|
||||
}
|
||||
});
|
||||
|
||||
let currentDevice = $derived(
|
||||
$serialPort
|
||||
? `${$serialPort.device.toLowerCase()}_${$serialPort.chipset.toLowerCase()}`
|
||||
: undefined,
|
||||
);
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
{#each data.devices as device}
|
||||
<li>
|
||||
<a href="./{device.name}/" class:highlight={device.name === currentDevice}
|
||||
>{device.name}</a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if !currentDevice}
|
||||
<aside transition:slide>Connect your device to see which one you need</aside>
|
||||
{/if}
|
||||
|
||||
<input type="file" accept=".bin" bind:files />
|
||||
|
||||
<style lang="scss">
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
outline: 1px solid var(--md-sys-color-outline);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
background-color 200ms ease,
|
||||
color 200ms ease,
|
||||
outline-offset 200ms ease,
|
||||
outline-color 200ms ease;
|
||||
}
|
||||
|
||||
@keyframes highlight {
|
||||
0% {
|
||||
outline-offset: 0;
|
||||
}
|
||||
100% {
|
||||
outline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wiggle {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
scale: 1;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-5deg);
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
outline-width: 2px;
|
||||
outline-color: var(--md-sys-color-primary);
|
||||
animation: wiggle 500ms ease 2 alternate;
|
||||
background-color: var(--md-sys-color-primary-container);
|
||||
color: var(--md-sys-color-on-primary-container);
|
||||
}
|
||||
</style>
|
||||
|
||||
9
src/routes/(app)/ota-update/+page.ts
Normal file
9
src/routes/(app)/ota-update/+page.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { PageLoad } from "./$types";
|
||||
import type { DirectoryListing } from "./listing";
|
||||
|
||||
export const load = (async ({ fetch }) => {
|
||||
const result = await fetch(import.meta.env.VITE_FIRMWARE_URL);
|
||||
const data = await result.json();
|
||||
|
||||
return { devices: data as DirectoryListing[] };
|
||||
}) satisfies PageLoad;
|
||||
85
src/routes/(app)/ota-update/[device]/+page.svelte
Normal file
85
src/routes/(app)/ota-update/[device]/+page.svelte
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
let { data } = $props();
|
||||
|
||||
let showPrerelease = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
<h2>Versions available for <em>{data.device}</em></h2>
|
||||
<label
|
||||
>Include Pre-releases<input
|
||||
type="checkbox"
|
||||
bind:checked={showPrerelease}
|
||||
/></label
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if data.versions}
|
||||
<ul>
|
||||
{#each data.versions as version}
|
||||
{@const isPrerelease = version.name.includes("-")}
|
||||
<li class:pre-release={isPrerelease}>
|
||||
<a href="./{version.name}/"
|
||||
>{version.name}
|
||||
<time datetime={version.mtime}
|
||||
>{new Date(version.mtime).toLocaleDateString()}</time
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<h2>The device {data.device} does not exist.</h2>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.pre-release {
|
||||
margin-inline-start: 2em;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
height: 2em;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
height 200ms ease,
|
||||
opacity 200ms ease;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
h2 {
|
||||
margin-block-end: 0;
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time {
|
||||
opacity: 0.5;
|
||||
&:before {
|
||||
content: "•";
|
||||
padding-inline: 0.4ch;
|
||||
}
|
||||
}
|
||||
|
||||
div.title:has(input:not(:checked)) ~ ul .pre-release {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
16
src/routes/(app)/ota-update/[device]/+page.ts
Normal file
16
src/routes/(app)/ota-update/[device]/+page.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { PageLoad } from "./$types";
|
||||
import type { DirectoryListing } from "../listing";
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const result = await fetch(
|
||||
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/`,
|
||||
);
|
||||
const data = await result.json();
|
||||
|
||||
return {
|
||||
versions: (data as DirectoryListing[]).sort((a, b) =>
|
||||
b.name.localeCompare(a.name),
|
||||
),
|
||||
device: params.device,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
163
src/routes/(app)/ota-update/[device]/[version]/+page.svelte
Normal file
163
src/routes/(app)/ota-update/[device]/[version]/+page.svelte
Normal file
@@ -0,0 +1,163 @@
|
||||
<script lang="ts">
|
||||
import { serialPort } from "$lib/serial/connection";
|
||||
import { slide } from "svelte/transition";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let currentDevice = $derived(
|
||||
$serialPort
|
||||
? `${$serialPort.device.toLowerCase()}_${$serialPort.chipset.toLowerCase()}`
|
||||
: undefined,
|
||||
);
|
||||
let isCorrectDevice = $derived(
|
||||
currentDevice ? currentDevice === data.device : undefined,
|
||||
);
|
||||
|
||||
let uf2Url = $derived(
|
||||
data.uf2
|
||||
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.uf2.name}`
|
||||
: undefined,
|
||||
);
|
||||
let otaUrl = $derived(
|
||||
data.ota
|
||||
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.ota.name}`
|
||||
: undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* Bytes to respective units
|
||||
*/
|
||||
function toByteUnit(value: number) {
|
||||
if (value < 1024) {
|
||||
return `${value}B`;
|
||||
} else if (value < 1024 * 1024) {
|
||||
return `${(value / 1024).toFixed(2)}KB`;
|
||||
} else {
|
||||
return `${(value / 1024 / 1024).toFixed(2)}MB`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h2>
|
||||
Update <em
|
||||
class="device"
|
||||
class:correct-device={isCorrectDevice === true}
|
||||
class:incorrect-device={isCorrectDevice === false}>{data.device}</em
|
||||
>
|
||||
to <em class="version">{data.version}</em>
|
||||
</h2>
|
||||
|
||||
<ul class="files">
|
||||
{#if data.uf2}
|
||||
<li>
|
||||
<a target="_blank" download href={uf2Url}
|
||||
>{data.uf2.name} <span class="icon">download</span><span class="size"
|
||||
>{toByteUnit(data.uf2.size)}</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{#if data.ota}
|
||||
<li>
|
||||
<a target="_blank" download href={otaUrl}
|
||||
>{data.ota.name} <span class="icon">download</span><span class="size"
|
||||
>{toByteUnit(data.uf2.size)}</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
|
||||
{#if isCorrectDevice === false}
|
||||
<div transition:slide class="incorrect-device">
|
||||
These files are incompatible with your device
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<section>
|
||||
<h3>OTA Upate</h3>
|
||||
{#if data.ota}
|
||||
<p>OTA update</p>
|
||||
{:else}
|
||||
<em>There are no OTA files for this device.</em>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3>Other options</h3>
|
||||
|
||||
<section>
|
||||
<h4>Via UF2</h4>
|
||||
<ol>
|
||||
<li>Backup your device</li>
|
||||
<li>Reboot to bootloader</li>
|
||||
<li>Save CURRENT.UF2 to the new drive</li>
|
||||
<li>Restore</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section>
|
||||
<h4>Via Serial</h4>
|
||||
<p>WIP</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
h2 > em {
|
||||
font-style: normal;
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
|
||||
hr {
|
||||
color: var(--md-sys-color-outline);
|
||||
margin-block: 3em;
|
||||
margin-inline: 5em;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.files {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
gap: 8px;
|
||||
|
||||
a {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: 1fr;
|
||||
border: 1px solid var(--md-sys-color-outline);
|
||||
border-radius: 8px;
|
||||
font-size: 0.9em;
|
||||
height: auto;
|
||||
|
||||
.size {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding-inline-start: 0.4em;
|
||||
grid-column: 2;
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
color: var(--md-sys-color-secondary);
|
||||
}
|
||||
|
||||
.device {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.correct-device {
|
||||
color: var(--md-sys-color-primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.incorrect-device {
|
||||
color: var(--md-sys-color-error);
|
||||
}
|
||||
</style>
|
||||
20
src/routes/(app)/ota-update/[device]/[version]/+page.ts
Normal file
20
src/routes/(app)/ota-update/[device]/[version]/+page.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { PageLoad } from "./$types";
|
||||
import type { FileListing, Listing } from "../../listing";
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const result = await fetch(
|
||||
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`,
|
||||
);
|
||||
const data: Listing[] = await result.json();
|
||||
|
||||
return {
|
||||
uf2: data.find(
|
||||
(entry) => entry.type === "file" && entry.name === "CURRENT.UF2",
|
||||
) as FileListing,
|
||||
ota: data.find(
|
||||
(entry) => entry.type === "file" && entry.name === "firmware.bin",
|
||||
),
|
||||
version: params.version,
|
||||
device: params.device,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
14
src/routes/(app)/ota-update/listing.ts
Normal file
14
src/routes/(app)/ota-update/listing.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type Listing = FileListing | DirectoryListing;
|
||||
|
||||
export interface DirectoryListing {
|
||||
name: string;
|
||||
type: "directory";
|
||||
mtime: string;
|
||||
}
|
||||
|
||||
export interface FileListing {
|
||||
name: string;
|
||||
type: "file";
|
||||
mtime: string;
|
||||
size: number;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ process.env["VITE_LEARN_URL"] = "https://www.iq-eq.io/";
|
||||
process.env["VITE_LATEST_FIRMWARE"] = "1.1.4";
|
||||
process.env["VITE_STORE_URL"] = "https://www.charachorder.com/";
|
||||
process.env["VITE_MATRIX_URL"] = "https://charachorder.io/";
|
||||
process.env["VITE_FIRMWARE_URL"] = "https://charachorder.io/firmware/";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
@@ -54,10 +55,11 @@ export default defineConfig({
|
||||
workbox: {
|
||||
// https://vite-pwa-org.netlify.app/frameworks/sveltekit.html#globpatterns
|
||||
globPatterns: [
|
||||
"client/**/*.{js,map,css,ico,woff2,csv,png,webp,svg,webmanifest}",
|
||||
"client/**/*.{js,css,ico,woff2,csv,png,webp,svg,webmanifest}",
|
||||
"prerendered/**/*.html",
|
||||
],
|
||||
ignoreURLParametersMatching: [/^import|redirectUrl|loginToken$/],
|
||||
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
||||
},
|
||||
manifest: {
|
||||
name: "CharaChorder Device Manager",
|
||||
|
||||
Reference in New Issue
Block a user