P0: scaffold Vite + Vue 3 + Tauri v2
- pnpm + TS + Tailwind 3 + Pinia + Vue Router with hash history - 6 placeholder views (Status, Peers, Routes, Messages, Topics, Settings) rendered via lucide-icon sidebar in App.vue - Tauri v2: shell, store, log, dialog plugins; bundle targets deb + appimage; sidecar wired via externalBin = binaries/mycelium - scripts/fetch-mycelium.sh pins v0.6.1, maps musl asset onto gnu target triple expected by Tauri bundler - CI: pnpm typecheck + cargo fmt/clippy/test
This commit is contained in:
52
.github/workflows/ci.yml
vendored
Normal file
52
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm typecheck
|
||||||
|
|
||||||
|
backend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install system deps
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
libwebkit2gtk-4.1-dev \
|
||||||
|
libjavascriptcoregtk-4.1-dev \
|
||||||
|
libsoup-3.0-dev \
|
||||||
|
libayatana-appindicator3-dev \
|
||||||
|
librsvg2-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libxdo-dev \
|
||||||
|
pkg-config \
|
||||||
|
build-essential
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: src-tauri
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
|
working-directory: src-tauri
|
||||||
|
- run: cargo clippy --all-targets --locked -- -D warnings
|
||||||
|
working-directory: src-tauri
|
||||||
|
- run: cargo test --locked
|
||||||
|
working-directory: src-tauri
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -12,9 +12,9 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Tauri
|
# Tauri
|
||||||
/app/src-tauri/target
|
/src-tauri/target
|
||||||
/app/src-tauri/binaries/mycelium*
|
/src-tauri/binaries/mycelium-*
|
||||||
/app/src-tauri/gen/schemas
|
/src-tauri/gen
|
||||||
|
|
||||||
# Editor / OS
|
# Editor / OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
12
index.html
Normal file
12
index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Mycellium UI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "mycellium-ui",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "vue-tsc --noEmit",
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-log": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-store": "^2.2.0",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-vue-next": "^0.469.0",
|
||||||
|
"pinia": "^2.3.0",
|
||||||
|
"radix-vue": "^1.9.11",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.2.0",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "~5.7.2",
|
||||||
|
"vite": "^6.0.7",
|
||||||
|
"vue-tsc": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1980
pnpm-lock.yaml
generated
Normal file
1980
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
65
scripts/fetch-mycelium.sh
Executable file
65
scripts/fetch-mycelium.sh
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fetches the official mycelium release binary and places it in src-tauri/binaries/
|
||||||
|
# with the target-triple suffix expected by Tauri's externalBin bundler.
|
||||||
|
#
|
||||||
|
# Usage: scripts/fetch-mycelium.sh [VERSION]
|
||||||
|
# VERSION defaults to MYCELIUM_VERSION below.
|
||||||
|
|
||||||
|
MYCELIUM_VERSION="${1:-${MYCELIUM_VERSION:-v0.6.1}}"
|
||||||
|
REPO="threefoldtech/mycelium"
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
DEST_DIR="${ROOT_DIR}/src-tauri/binaries"
|
||||||
|
mkdir -p "${DEST_DIR}"
|
||||||
|
|
||||||
|
detect_target_triple() {
|
||||||
|
local arch os
|
||||||
|
arch="$(uname -m)"
|
||||||
|
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||||
|
case "${os}-${arch}" in
|
||||||
|
linux-x86_64) echo "x86_64-unknown-linux-gnu" ;;
|
||||||
|
linux-aarch64) echo "aarch64-unknown-linux-gnu" ;;
|
||||||
|
*) echo "unsupported: ${os}-${arch}" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map our target triple to the asset name pattern used by upstream releases.
|
||||||
|
asset_for_triple() {
|
||||||
|
case "$1" in
|
||||||
|
x86_64-unknown-linux-gnu) echo "mycelium-x86_64-unknown-linux-musl.tar.gz" ;;
|
||||||
|
aarch64-unknown-linux-gnu) echo "mycelium-aarch64-unknown-linux-musl.tar.gz" ;;
|
||||||
|
*) echo "unsupported triple: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
TRIPLE="$(detect_target_triple)"
|
||||||
|
ASSET="$(asset_for_triple "${TRIPLE}")"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${MYCELIUM_VERSION}/${ASSET}"
|
||||||
|
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||||||
|
|
||||||
|
echo "→ downloading ${URL}"
|
||||||
|
curl -fsSL "${URL}" -o "${TMP_DIR}/mycelium.tar.gz"
|
||||||
|
|
||||||
|
echo "→ extracting"
|
||||||
|
tar -xzf "${TMP_DIR}/mycelium.tar.gz" -C "${TMP_DIR}"
|
||||||
|
|
||||||
|
# The archive contains a single 'mycelium' binary at the root.
|
||||||
|
SRC="${TMP_DIR}/mycelium"
|
||||||
|
if [[ ! -f "${SRC}" ]]; then
|
||||||
|
# Some releases nest the binary; find it.
|
||||||
|
SRC="$(find "${TMP_DIR}" -name 'mycelium' -type f -executable | head -n1)"
|
||||||
|
fi
|
||||||
|
if [[ -z "${SRC}" || ! -f "${SRC}" ]]; then
|
||||||
|
echo "could not locate mycelium binary in archive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEST="${DEST_DIR}/mycelium-${TRIPLE}"
|
||||||
|
install -m 0755 "${SRC}" "${DEST}"
|
||||||
|
echo "✓ installed ${DEST}"
|
||||||
|
echo " version: ${MYCELIUM_VERSION}"
|
||||||
|
echo " size: $(stat -c%s "${DEST}") bytes"
|
||||||
5900
src-tauri/Cargo.lock
generated
Normal file
5900
src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
src-tauri/Cargo.toml
Normal file
33
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[package]
|
||||||
|
name = "mycellium-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Mycelium overlay network desktop client"
|
||||||
|
authors = ["syoul"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mycellium_ui_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-shell = "2"
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
tauri-plugin-log = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
|
thiserror = "2"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
portpicker = "0.1"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
3
src-tauri/build.rs
Normal file
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
12
src-tauri/capabilities/default.json
Normal file
12
src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capabilities for the main window. The mycelium sidecar is spawned from Rust setup and does not require JS-side shell permissions.",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"store:default",
|
||||||
|
"log:default",
|
||||||
|
"dialog:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
src-tauri/icons/128x128.png
Normal file
BIN
src-tauri/icons/128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
BIN
src-tauri/icons/128x128@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
BIN
src-tauri/icons/32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/icon.png
Normal file
BIN
src-tauri/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
21
src-tauri/src/lib.rs
Normal file
21
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
|
||||||
|
)
|
||||||
|
.with_target(false)
|
||||||
|
.compact()
|
||||||
|
.try_init()
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_log::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
5
src-tauri/src/main.rs
Normal file
5
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mycellium_ui_lib::run();
|
||||||
|
}
|
||||||
47
src-tauri/tauri.conf.json
Normal file
47
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "Mycellium UI",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "tech.threefold.mycellium-ui",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"beforeBuildCommand": "pnpm build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": false,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"label": "main",
|
||||||
|
"title": "Mycellium",
|
||||||
|
"width": 1100,
|
||||||
|
"height": 720,
|
||||||
|
"minWidth": 800,
|
||||||
|
"minHeight": 600,
|
||||||
|
"resizable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": ["deb", "appimage"],
|
||||||
|
"category": "Network",
|
||||||
|
"shortDescription": "Mycelium overlay network client",
|
||||||
|
"longDescription": "Desktop GUI for the Mycelium end-to-end encrypted IPv6 overlay network.",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png"
|
||||||
|
],
|
||||||
|
"externalBin": ["binaries/mycelium"],
|
||||||
|
"linux": {
|
||||||
|
"deb": {
|
||||||
|
"depends": ["policykit-1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/App.vue
Normal file
61
src/App.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { RouterLink, RouterView, useRoute } from "vue-router";
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
Users,
|
||||||
|
Route as RouteIcon,
|
||||||
|
MessageSquare,
|
||||||
|
Hash,
|
||||||
|
Settings as SettingsIcon,
|
||||||
|
} from "lucide-vue-next";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ to: "/status", label: "Status", icon: Activity },
|
||||||
|
{ to: "/peers", label: "Peers", icon: Users },
|
||||||
|
{ to: "/routes", label: "Routes", icon: RouteIcon },
|
||||||
|
{ to: "/messages", label: "Messages", icon: MessageSquare },
|
||||||
|
{ to: "/topics", label: "Topics", icon: Hash },
|
||||||
|
{ to: "/settings", label: "Settings", icon: SettingsIcon },
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentTitle = computed(() => (route.meta?.title as string) ?? "Mycellium");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-screen w-screen overflow-hidden bg-background text-foreground">
|
||||||
|
<aside
|
||||||
|
class="flex w-56 shrink-0 flex-col border-r border-border bg-card"
|
||||||
|
>
|
||||||
|
<div class="flex h-14 items-center px-4 border-b border-border">
|
||||||
|
<span class="font-semibold text-base">Mycellium</span>
|
||||||
|
</div>
|
||||||
|
<nav class="flex-1 overflow-y-auto px-2 py-3 space-y-1">
|
||||||
|
<RouterLink
|
||||||
|
v-for="item in navItems"
|
||||||
|
:key="item.to"
|
||||||
|
:to="item.to"
|
||||||
|
class="flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors"
|
||||||
|
active-class="bg-secondary text-secondary-foreground"
|
||||||
|
exact-active-class="bg-secondary text-secondary-foreground"
|
||||||
|
>
|
||||||
|
<component :is="item.icon" class="h-4 w-4" />
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</RouterLink>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="flex flex-1 flex-col overflow-hidden">
|
||||||
|
<header
|
||||||
|
class="flex h-14 items-center border-b border-border px-6 shrink-0"
|
||||||
|
>
|
||||||
|
<h1 class="text-lg font-semibold">{{ currentTitle }}</h1>
|
||||||
|
</header>
|
||||||
|
<div class="flex-1 overflow-y-auto p-6">
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
7
src/env.d.ts
vendored
Normal file
7
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module "*.vue" {
|
||||||
|
import type { DefineComponent } from "vue";
|
||||||
|
const component: DefineComponent<object, object, unknown>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
23
src/lib/utils.ts
Normal file
23
src/lib/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return "0 B";
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return `${(bytes / Math.pow(k, i)).toFixed(i === 0 ? 0 : 2)} ${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatRate(bytesPerSec: number): string {
|
||||||
|
return `${formatBytes(bytesPerSec)}/s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shortenIpv6(addr: string): string {
|
||||||
|
if (addr.length <= 24) return addr;
|
||||||
|
return `${addr.slice(0, 10)}…${addr.slice(-8)}`;
|
||||||
|
}
|
||||||
10
src/main.ts
Normal file
10
src/main.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import { router } from "./router";
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
app.mount("#app");
|
||||||
49
src/router/index.ts
Normal file
49
src/router/index.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { createRouter, createWebHashHistory, type RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
redirect: "/status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/status",
|
||||||
|
name: "status",
|
||||||
|
component: () => import("@/views/Status.vue"),
|
||||||
|
meta: { title: "Status" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/peers",
|
||||||
|
name: "peers",
|
||||||
|
component: () => import("@/views/Peers.vue"),
|
||||||
|
meta: { title: "Peers" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/routes",
|
||||||
|
name: "routes",
|
||||||
|
component: () => import("@/views/Routes.vue"),
|
||||||
|
meta: { title: "Routes" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/messages",
|
||||||
|
name: "messages",
|
||||||
|
component: () => import("@/views/Messages.vue"),
|
||||||
|
meta: { title: "Messages" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/topics",
|
||||||
|
name: "topics",
|
||||||
|
component: () => import("@/views/Topics.vue"),
|
||||||
|
meta: { title: "Topics" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/settings",
|
||||||
|
name: "settings",
|
||||||
|
component: () => import("@/views/Settings.vue"),
|
||||||
|
meta: { title: "Settings" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
57
src/style.css
Normal file
57
src/style.css
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
--primary: 240 5.9% 10%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
--ring: 240 5.9% 10%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/views/Messages.vue
Normal file
5
src/views/Messages.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p class="text-sm text-muted-foreground">Messages view — wired in P4.</p>
|
||||||
|
</template>
|
||||||
5
src/views/Peers.vue
Normal file
5
src/views/Peers.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p class="text-sm text-muted-foreground">Peers view — wired in P2.</p>
|
||||||
|
</template>
|
||||||
5
src/views/Routes.vue
Normal file
5
src/views/Routes.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p class="text-sm text-muted-foreground">Routes view — wired in P3.</p>
|
||||||
|
</template>
|
||||||
5
src/views/Settings.vue
Normal file
5
src/views/Settings.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p class="text-sm text-muted-foreground">Settings view — wired in P5.</p>
|
||||||
|
</template>
|
||||||
9
src/views/Status.vue
Normal file
9
src/views/Status.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
Daemon status will appear here once the sidecar is wired up (P1).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
5
src/views/Topics.vue
Normal file
5
src/views/Topics.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p class="text-sm text-muted-foreground">Topics view — wired in P4.</p>
|
||||||
|
</template>
|
||||||
50
tailwind.config.ts
Normal file
50
tailwind.config.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
darkMode: "class",
|
||||||
|
content: ["./index.html", "./src/**/*.{vue,ts,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
mono: ["ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "monospace"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
} satisfies Config;
|
||||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"types": ["vite/client"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
11
tsconfig.node.json
Normal file
11
tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
30
vite.config.ts
Normal file
30
vite.config.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
host: host || false,
|
||||||
|
hmr: host
|
||||||
|
? {
|
||||||
|
protocol: "ws",
|
||||||
|
host,
|
||||||
|
port: 1421,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user