commit 81baed85c7d2a73117fc8ff555aeabda367dd6a2 Author: GnomeZworc Date: Sat Jun 6 23:12:42 2026 +0200 initial commit Signed-off-by: GnomeZworc diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b75143c --- /dev/null +++ b/build.sh @@ -0,0 +1,332 @@ +#!/usr/bin/env bash +# Build the web UI component set from components.yml. +# +# ./build.sh build (download remote components + generate components.json) +# ./build.sh --check validate manifest and entry files without downloading +# +# Reads components.yml, downloads each remote component into components// +# at the requested ref, then generates components.json (runtime load list) and +# components.lock.json (resolved commit pins). +# +# Dependencies: yq (mikefarah/yq v4), jq, curl +# Optional env: +# GIT_TOKEN forge token for private repos (sent as "Authorization: token …") + +set -euo pipefail + +WEB_DIR="$(cd "$(dirname "$0")" && pwd)" +MANIFEST="${WEB_DIR}/components.yml" +COMPONENTS_DIR="${WEB_DIR}/components" +OUTPUT="${WEB_DIR}/components.json" +LOCKFILE="${WEB_DIR}/components.lock.json" +GIT_TOKEN="${GIT_TOKEN:-}" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +info() { echo -e "${BLUE}[i]${NC} $*"; } +die() { echo -e "${RED}[-]${NC} $*" >&2; exit 1; } + +CHECK_ONLY=false +[[ "${1:-}" == "--check" ]] && CHECK_ONLY=true + +# ── Dependency + manifest guards ──────────────────────────────────────────────── + +command -v yq &>/dev/null || die "yq is required (mikefarah/yq v4)" +command -v jq &>/dev/null || die "jq is required" +command -v curl &>/dev/null || die "curl is required" +[[ -f "$MANIFEST" ]] || die "manifest not found: $MANIFEST" + +# ── Forge API helpers ─────────────────────────────────────────────────────────── + +api_get() { + local url="$1" + local args=(-sf -H "Accept: application/json") + [[ -n "$GIT_TOKEN" ]] && args+=(-H "Authorization: token ${GIT_TOKEN}") + curl "${args[@]}" "$url" || die "API request failed: $url" +} + +raw_dl() { + local url="$1" out="$2" + local args=(-sfL) + [[ -n "$GIT_TOKEN" ]] && args+=(-H "Authorization: token ${GIT_TOKEN}") + curl "${args[@]}" "$url" -o "$out" || die "download failed: $url" +} + +# Split a repo URL into "server owner repo". +# https://git.g3e.fr/team-reseau/vpc-panel → https://git.g3e.fr team-reseau vpc-panel +parse_repo_url() { + local url="$1" + url="${url%.git}"; url="${url%/}" + local proto="${url%%://*}" + local rest="${url#*://}" + local host="${rest%%/*}" + local path="${rest#*/}" + [[ "$path" == "$rest" || -z "$path" ]] && die "invalid repo URL (need owner/repo): $1" + local owner="${path%/*}" repo="${path##*/}" + [[ -z "$owner" || -z "$repo" ]] && die "invalid repo URL (need owner/repo): $1" + echo "${proto}://${host}" "$owner" "$repo" +} + +# Build the commits API URL — handles GitHub (api.github.com) vs Gitea (/api/v1). +forge_commits_url() { + local server="$1" owner="$2" repo="$3" ref="$4" + if [[ "$server" == "https://github.com" ]]; then + echo "https://api.github.com/repos/${owner}/${repo}/commits?sha=${ref}&per_page=1" + else + echo "${server}/api/v1/repos/${owner}/${repo}/commits?sha=${ref}&limit=1" + fi +} + +# Build the contents API URL for a given path (empty = repo root). +forge_contents_url() { + local server="$1" owner="$2" repo="$3" ref="$4" path="$5" + if [[ "$server" == "https://github.com" ]]; then + if [[ -z "$path" ]]; then + echo "https://api.github.com/repos/${owner}/${repo}/contents?ref=${ref}" + else + echo "https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${ref}" + fi + else + if [[ -z "$path" ]]; then + echo "${server}/api/v1/repos/${owner}/${repo}/contents?ref=${ref}" + else + echo "${server}/api/v1/repos/${owner}/${repo}/contents/${path}?ref=${ref}" + fi + fi +} + +resolve_commit() { + local server="$1" owner="$2" repo="$3" ref="$4" + api_get "$(forge_commits_url "$server" "$owner" "$repo" "$ref")" \ + | jq -r '.[0].sha // empty' +} + +# Recursively download a repo path (relative to repo root) into dest dir. +download_path() { + local server="$1" owner="$2" repo="$3" ref="$4" rpath="$5" dest="$6" + + local api + api="$(forge_contents_url "$server" "$owner" "$repo" "$ref" "$rpath")" + + local listing + listing="$(api_get "$api")" + + mkdir -p "$dest" + while IFS= read -r entry; do + local type name dl path + type="$(jq -r '.type' <<<"$entry")" + name="$(jq -r '.name' <<<"$entry")" + dl="$( jq -r '.download_url // ""' <<<"$entry")" + path="$(jq -r '.path' <<<"$entry")" + + if [[ "$type" == "dir" ]]; then + download_path "$server" "$owner" "$repo" "$ref" "$path" "${dest}/${name}" + else + [[ -z "$dl" ]] && die "no download_url for ${path} in ${owner}/${repo}@${ref}" + raw_dl "$dl" "${dest}/${name}" + fi + done < <(echo "$listing" | jq -c 'if type=="array" then .[] else . end') +} + +# ── Read manifest ──────────────────────────────────────────────────────────────── + +PAGES_DIR="${WEB_DIR}/pages" + +mapfile -t LOCALS < <(yq '(.local_components // [])[]' "$MANIFEST") +mapfile -t REMOTES < <(yq '(.components // [])[] | [.name, .repo, (.ref // "main")] | join("|")' "$MANIFEST") +mapfile -t LOCAL_PAGES < <(yq '(.local_pages // [])[]' "$MANIFEST") +mapfile -t REMOTE_PAGES < <(yq '(.remote_pages // [])[] | [.name, .repo, (.ref // "main")] | join("|")' "$MANIFEST") +mapfile -t VENDORS < <(yq '(.vendors // [])[] | [.name, .repo, (.ref // "main"), (.path // ""), (.dest // "")] | join("|")' "$MANIFEST") + +# ── Download vendors ──────────────────────────────────────────────────────────── +# Vendors are third-party libraries downloaded into web/vendor/ at build time. +# They are gitignored and never loaded by components.json — components import +# them directly via relative paths (e.g. ../../vendor/novnc/core/rfb.js). + +for spec in "${VENDORS[@]}"; do + [[ -z "$spec" ]] && continue + IFS='|' read -r name repo ref vpath dest <<<"$spec" + [[ -z "$name" || -z "$repo" ]] && die "vendor entry missing name or repo: '$spec'" + + read -r server owner gitrepo <<<"$(parse_repo_url "$repo")" + [[ -z "$dest" ]] && dest="vendor/${name}" + local_dest="${WEB_DIR}/${dest}" + + if [[ "$CHECK_ONLY" == true ]]; then + info "would fetch vendor ${name} from ${repo}@${ref} path=${vpath:-/}" + continue + fi + + log "fetching vendor ${name} ← ${owner}/${gitrepo}@${ref}${vpath:+ path=}${vpath}" + commit="$(resolve_commit "$server" "$owner" "$gitrepo" "$ref")" + [[ -z "$commit" ]] && die "cannot resolve ref '${ref}' in ${owner}/${gitrepo}" + + rm -rf "$local_dest" + download_path "$server" "$owner" "$gitrepo" "$ref" "$vpath" "$local_dest" + info " vendor '${name}' → ${dest} (${commit:0:12})" +done + +# Ordered list of load paths for components.json +declare -a LOAD_PATHS=() + +# ── Validate locals ────────────────────────────────────────────────────────────── + +for name in "${LOCALS[@]}"; do + [[ -z "$name" ]] && continue + entry="${COMPONENTS_DIR}/${name}/${name}.js" + if [[ ! -f "$entry" ]]; then + die "local component '${name}' missing entry file: components/${name}/${name}.js" + fi + LOAD_PATHS+=("components/${name}/${name}.js") +done + +# ── Download remotes ───────────────────────────────────────────────────────────── + +LOCK_ENTRIES="[]" + +for spec in "${REMOTES[@]}"; do + [[ -z "$spec" ]] && continue + IFS='|' read -r name repo ref <<<"$spec" + [[ -z "$name" || -z "$repo" ]] && die "manifest entry missing name or repo: '$spec'" + + read -r server owner gitrepo <<<"$(parse_repo_url "$repo")" + dest="${COMPONENTS_DIR}/${name}" + + if [[ "$CHECK_ONLY" == true ]]; then + info "would fetch ${name} from ${repo}@${ref}" + commit="$(resolve_commit "$server" "$owner" "$gitrepo" "$ref" || true)" + [[ -z "$commit" ]] && warn " could not resolve ref '${ref}' in ${owner}/${gitrepo}" + LOAD_PATHS+=("components/${name}/${name}.js") + continue + fi + + log "fetching ${name} ← ${owner}/${gitrepo}@${ref}" + commit="$(resolve_commit "$server" "$owner" "$gitrepo" "$ref")" + [[ -z "$commit" ]] && die "cannot resolve ref '${ref}' in ${owner}/${gitrepo}" + + rm -rf "$dest" + download_path "$server" "$owner" "$gitrepo" "$ref" "" "$dest" + + entry="${dest}/${name}.js" + [[ -f "$entry" ]] || die "component '${name}' has no ${name}.js at repo root" + + LOAD_PATHS+=("components/${name}/${name}.js") + LOCK_ENTRIES="$(jq \ + --arg name "$name" --arg repo "$repo" --arg ref "$ref" --arg commit "$commit" \ + '. + [{name:$name, repo:$repo, ref:$ref, commit:$commit}]' <<<"$LOCK_ENTRIES")" + info " pinned ${commit:0:12}" +done + +# ── Generate components.json ───────────────────────────────────────────────────── + +if [[ "$CHECK_ONLY" == true ]]; then + log "check passed: ${#LOAD_PATHS[@]} components, manifest valid" + exit 0 +fi + +printf '%s\n' "${LOAD_PATHS[@]}" | jq -R . | jq -s . > "$OUTPUT" +echo "$LOCK_ENTRIES" | jq '.' > "$LOCKFILE" + +log "wrote ${OUTPUT} (${#LOAD_PATHS[@]} components)" +log "wrote ${LOCKFILE} ($(jq 'length' <<<"$LOCK_ENTRIES") remote pins)" + +# ── Process pages ──────────────────────────────────────────────────────────────── + +PAGE_ENTRIES="[]" + +# Collect page metadata from a directory into PAGE_ENTRIES. +# Detects css/js presence automatically — both are optional. +add_page_entry() { + local name="$1" dir="$2" + local manifest="${dir}/manifest.json" + local html="${dir}/index.html" + + [[ -f "$manifest" ]] || die "page '${name}' missing manifest.json in ${dir}" + [[ -f "$html" ]] || die "page '${name}' missing index.html in ${dir}" + + local route label icon css_arg js_arg + route=$(jq -r '.route' "$manifest") + label=$(jq -r '.label' "$manifest") + icon=$(jq -r '.icon' "$manifest") + css_arg="null"; js_arg="null" + [[ -f "${dir}/${name}.css" ]] && css_arg="\"pages/${name}/${name}.css\"" + [[ -f "${dir}/${name}.js" ]] && js_arg="\"pages/${name}/${name}.js\"" + + PAGE_ENTRIES="$(jq \ + --arg route "$route" --arg label "$label" --arg icon "$icon" \ + --arg html "pages/${name}/index.html" \ + --argjson css "$css_arg" --argjson js "$js_arg" \ + '. + [{route:$route, label:$label, icon:$icon, html:$html, css:$css, js:$js}]' \ + <<<"$PAGE_ENTRIES")" + info " page '${name}' → /#/${route}$([ "$css_arg" != "null" ] && echo " +css")$([ "$js_arg" != "null" ] && echo " +js")" +} + +# Local pages +for name in "${LOCAL_PAGES[@]}"; do + [[ -z "$name" ]] && continue + add_page_entry "$name" "${PAGES_DIR}/${name}" +done + +# Remote pages — download then register +for spec in "${REMOTE_PAGES[@]}"; do + [[ -z "$spec" ]] && continue + IFS='|' read -r name repo ref <<<"$spec" + [[ -z "$name" || -z "$repo" ]] && die "remote_pages entry missing name or repo" + + read -r server owner gitrepo <<<"$(parse_repo_url "$repo")" + dest="${PAGES_DIR}/${name}" + + if [[ "$CHECK_ONLY" == true ]]; then + info "would fetch page ${name} from ${repo}@${ref}" + continue + fi + + log "fetching page ${name} ← ${owner}/${gitrepo}@${ref}" + commit="$(resolve_commit "$server" "$owner" "$gitrepo" "$ref")" + [[ -z "$commit" ]] && die "cannot resolve ref '${ref}' in ${owner}/${gitrepo}" + + rm -rf "$dest" + download_path "$server" "$owner" "$gitrepo" "$ref" "" "$dest" + add_page_entry "$name" "$dest" + info " pinned ${commit:0:12}" +done + +if [[ "$CHECK_ONLY" == false ]]; then + # Build navigation.json from menu: section. + # Convert full manifest to JSON once (single yq call) then jq handles all logic. + # Menu items: string → page lookup, object → pass-through (separator/section). + manifest_json=$(yq -o json '.' "$MANIFEST") + menu_json=$(echo "$manifest_json" | jq -c '.menu // []') + menu_len=$(echo "$menu_json" | jq 'length') + + if [[ "$menu_len" -gt 0 ]]; then + NAV_ENTRIES=$(jq -n \ + --argjson menu "$menu_json" \ + --argjson pages "$PAGE_ENTRIES" \ + 'def resolve($pages): + . as $r | $pages[] | select(.route == $r) | { label, href: ("#/" + .route), icon }; + [ $menu[] | + if type == "string" + then resolve($pages) + elif .type == "group" + then { type: "group", label: .label, icon: (.icon // ""), + children: [ .children[]? | + if type == "string" then resolve($pages) else . end ] } + else . + end + ]') + else + # No menu defined — use all pages in discovery order + NAV_ENTRIES=$(echo "$PAGE_ENTRIES" | \ + jq '[.[] | { label, href: ("#/" + .route), icon }]') + fi + + # pages.json — full list (all pages, original discovery order) + echo "$PAGE_ENTRIES" | jq '.' > "${WEB_DIR}/pages.json" + + # navigation.json — write NAV_ENTRIES as-is (already correctly shaped) + echo "$NAV_ENTRIES" | jq '.' > "${WEB_DIR}/navigation.json" + + log "wrote pages.json ($(echo "$PAGE_ENTRIES" | jq 'length') pages) + navigation.json ($(echo "$NAV_ENTRIES" | jq 'length') in menu)" +fi diff --git a/components.lock.json b/components.lock.json new file mode 100644 index 0000000..0d4f101 --- /dev/null +++ b/components.lock.json @@ -0,0 +1,2 @@ +[ +] diff --git a/components.yml b/components.yml new file mode 100644 index 0000000..5d2a320 --- /dev/null +++ b/components.yml @@ -0,0 +1,53 @@ +# Component manifest — source of truth for the web UI. +# +# Edit this file, then run ./build.sh to: +# - download every remote component into components// +# - generate components.json (the runtime load list) in declared order +# +# Load order matters: service components (login-gate, api-client) must come +# before the components that consume them. + +# Local components shipped inside this repo. Not downloaded — listed here only +# so build.sh can place them in the generated load order. +local_components: [] + +# Remote components fetched from git at build time. +# Each is its own repo; its root must contain .js and manifest.json. +# +# - name: vpc-panel # → components/vpc-panel/ +# repo: https://git.g3e.fr/team-reseau/vpc-panel +# ref: v1.2.0 # tag, branch or commit (default: main) +components: + - name: side-nav + repo: https://git.g3e.fr/syonad/syn-side-nav + ref: main + +# Vendor libraries — downloaded into web/vendor/ at build time (gitignored). +# Components import them via relative paths, never via components.json. +# path: restrict download to a subdirectory of the repo (optional). +# dest: destination under web/ (default: vendor/). +vendors: [] + +# Pages — each page is a self-contained directory (JS + CSS + manifest.json). +# Same model as components: local pages ship in this repo, remote pages are +# downloaded from git at build time. build.sh reads each manifest.json to +# generate navigation.json and pages.json. +# +# Page manifest.json must contain: tag, route, label, icon. +# Page JS must: customElements.define('', ...) and use display:contents. + +local_pages: + - dashboard + +# Remote pages fetched from git — same mechanism as components. +# +# - name: vpcs +# repo: https://git.g3e.fr/team-reseau/page-vpcs +# ref: v1.0.0 +remote_pages: [] + +# Menu — order and visibility in the side-nav. +# List routes in desired display order. +# A page not listed here is accessible via /#/route but hidden from the nav. +menu: + - dashboard diff --git a/components/.keep b/components/.keep new file mode 100644 index 0000000..e69de29 diff --git a/config.json b/config.json new file mode 100644 index 0000000..bf6e218 --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "netbox_url": "http://netbox.local", + "netbox_token": "your-token-here", + "agent_url": "http://127.0.0.1:8080" +} diff --git a/core/config.js b/core/config.js new file mode 100644 index 0000000..51f9b56 --- /dev/null +++ b/core/config.js @@ -0,0 +1,4 @@ +const response = await fetch('./config.json') +if (!response.ok) throw new Error('Failed to load config.json') + +export const config = await response.json() diff --git a/core/menu.js b/core/menu.js new file mode 100644 index 0000000..0a2adad --- /dev/null +++ b/core/menu.js @@ -0,0 +1,37 @@ +// Dynamic menu loader. +// Fetches data for groups declared with empty children in navigation.json +// and injects them into side-nav after it is ready. +// +// Imported non-blocking from index.html — does not delay routing. +// Replace MOCK_* with real api-client calls when the backend is ready. + +// ── Mock data ──────────────────────────────────────────────────────────────── + +const MOCK_ACCOUNTS = [ + { id: 'tresorerie', name: 'Trésorerie' }, + { id: 'charges', name: 'Charges' }, + { id: 'produits', name: 'Produits' }, + { id: 'fournisseurs', name: 'Fournisseurs' }, +] + +async function fetchAccounts() { + // TODO: replace with real call + // const api = document.querySelector('api-client') + // return await api.finance.list('/accounts') + await new Promise(r => setTimeout(r, 500)) // simulate network latency + return MOCK_ACCOUNTS +} + +// ── Injection ──────────────────────────────────────────────────────────────── + +const nav = document.querySelector('side-nav') +if (!nav) throw new Error('menu.js: side-nav not found in DOM') + +await nav.ready + +const accounts = await fetchAccounts() +nav.setGroupChildren('Comptes', accounts.map(a => ({ + label: a.name, + href: `#/account?id=${a.id}`, + icon: 'account', +}))) diff --git a/core/registry.js b/core/registry.js new file mode 100644 index 0000000..56671cc --- /dev/null +++ b/core/registry.js @@ -0,0 +1,10 @@ +// Loads and registers all components listed in components.json. +// To add a component: git clone into components/, add the path here. +const response = await fetch('./components.json') +if (!response.ok) throw new Error('Failed to load components.json') + +const components = await response.json() + +for (const path of components) { + await import(`../${path}`) +} diff --git a/core/router.js b/core/router.js new file mode 100644 index 0000000..d23777c --- /dev/null +++ b/core/router.js @@ -0,0 +1,55 @@ +// Hash-based SPA router. +// Reads pages.json (generated by build.sh from components.yml). +// Each page is an HTML fragment (index.html) + optional CSS + optional JS module. +// +// Load order enforced by index.html: +// await import('./core/registry.js') ← defines all components +// await import('./core/router.js') ← this file + +const response = await fetch('./pages.json') +if (!response.ok) throw new Error('router: failed to load pages.json — run build.sh') +const PAGES = await response.json() + +// Cache of imported JS modules — modules are only fetched once per session. +const MODULE_CACHE = new Map() + +// Currently active page CSS — removed on navigation. +let activeCSS = null + +function currentRoute() { + const hash = window.location.hash.replace(/^#\//, '') || PAGES[0]?.route || '' + return hash.split('?')[0] +} + +async function render(routeName) { + const page = PAGES.find(p => p.route === routeName) ?? PAGES[0] + const main = document.querySelector('main') + if (!main || !page) return + + // ── HTML ──────────────────────────────────────────────────────────────────── + const htmlRes = await fetch(page.html) + if (!htmlRes.ok) throw new Error(`router: failed to load ${page.html}`) + main.innerHTML = await htmlRes.text() + + // ── CSS ───────────────────────────────────────────────────────────────────── + activeCSS?.remove() + activeCSS = null + if (page.css) { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = page.css + document.head.appendChild(link) + activeCSS = link + } + + // ── JS ────────────────────────────────────────────────────────────────────── + if (page.js) { + if (!MODULE_CACHE.has(page.js)) { + MODULE_CACHE.set(page.js, await import(`../${page.js}`)) + } + MODULE_CACHE.get(page.js)?.init?.(main) + } +} + +await render(currentRoute()) +window.addEventListener('hashchange', () => render(currentRoute())) diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..3b8d831 --- /dev/null +++ b/favicon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..1af8dea --- /dev/null +++ b/index.html @@ -0,0 +1,117 @@ + + + + + + two — dashboard + + + + + + + + +
+

two

+ network orchestrator +
+ +
+ +
+ + +
+
+ + + + + diff --git a/pages/dashboard/dashboard.css b/pages/dashboard/dashboard.css new file mode 100644 index 0000000..204e2c0 --- /dev/null +++ b/pages/dashboard/dashboard.css @@ -0,0 +1,2 @@ +/* Styles scoped to the dashboard page. + Applied when the route is active, removed on navigation. */ diff --git a/pages/dashboard/dashboard.js b/pages/dashboard/dashboard.js new file mode 100644 index 0000000..9df2f71 --- /dev/null +++ b/pages/dashboard/dashboard.js @@ -0,0 +1,57 @@ +import { SidePanel } from '../../components/side-panel/side-panel.js' + +export function init(main) { + main.querySelectorAll('[data-panel-title]').forEach(btn => { + btn.addEventListener('click', () => { + SidePanel.open({ + title: btn.dataset.panelTitle, + width: btn.dataset.panelWidth ?? '480px', + content: `

+ Contenu du panneau ${btn.dataset.panelTitle}.
+ Largeur : ${btn.dataset.panelWidth}.

+ Tu peux ouvrir plusieurs panneaux — ils s'empilent vers la gauche. + Ferme avec ✕, Échap, ou en cliquant le fond. +

`, + }) + }) + }) + + main.querySelectorAll('[data-action="create-vm"]').forEach(btn => { + btn.addEventListener('click', () => { + SidePanel.open({ + title: 'Nouvelle VM', + width: '520px', + content: ``, + }) + // form-vm gère son propre submit et dispatche vm-saved — pas besoin de querySelector + }) + }) + + main.querySelectorAll('[data-action="edit-vm-i-test1"]').forEach(btn => { + btn.addEventListener('click', () => { + SidePanel.open({ + title: 'Modifier i-test1', + width: '520px', + content: `` + }) + // form-vm gère son propre submit et dispatche vm-saved — pas besoin de querySelector + }) + }) + + main.querySelectorAll('[data-action="console"]').forEach(btn => { + btn.addEventListener('click', () => { + SidePanel.open({ + title: 'afficher une consol', + width: '700px', + content: `` + }) + // form-vm gère son propre submit et dispatche vm-saved — pas besoin de querySelector + }) + }) + + // vm-saved est attaché sur main (pas document) → nettoyé automatiquement + // quand le router remplace le contenu de
+ main.addEventListener('vm-saved', e => { + console.log('vm-saved', e.detail) + }) +} diff --git a/pages/dashboard/index.html b/pages/dashboard/index.html new file mode 100644 index 0000000..b27ce1d --- /dev/null +++ b/pages/dashboard/index.html @@ -0,0 +1 @@ +Cecie est la premiere page diff --git a/pages/dashboard/manifest.json b/pages/dashboard/manifest.json new file mode 100644 index 0000000..1a0f7ba --- /dev/null +++ b/pages/dashboard/manifest.json @@ -0,0 +1,6 @@ +{ + "route": "dashboard", + "label": "Dashboard", + "icon": "dashboard", + "version": "0.1.0" +} diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000..e69de29