From dd75acbcd942ff1383971126cf0ae9ce2015a238 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 14:18:00 +0000 Subject: [PATCH 01/39] Add Docker deployment and GitHub Actions workflow for racknerd1 Builds the Vite app, serves it via nginx in a Docker container, and deploys via SSH/SCP to racknerd1 on push to main. Also syncs the out-of-date package-lock.json so npm ci works in CI/Docker builds. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF --- .dockerignore | 6 +++ .github/workflows/deploy.yml | 39 +++++++++++++++++++ Dockerfile | 11 ++++++ docker-compose.yml | 8 ++++ nginx.conf | 10 +++++ package-lock.json | 75 +++++++++++++++++++++++++++--------- 6 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/deploy.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3a158f0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +.git +.github +pics +*.md diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..56597a7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: Deploy to racknerd1 + +on: + push: + branches: [main] + workflow_dispatch: {} + +env: + DEPLOY_PATH: /opt/archnest + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Copy repo to racknerd1 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.RACKNERD_HOST }} + username: ${{ secrets.RACKNERD_USER }} + key: ${{ secrets.RACKNERD_SSH_KEY }} + port: ${{ secrets.RACKNERD_PORT || 22 }} + source: "." + target: ${{ env.DEPLOY_PATH }} + rm: false + + - name: Build and restart container + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.RACKNERD_HOST }} + username: ${{ secrets.RACKNERD_USER }} + key: ${{ secrets.RACKNERD_SSH_KEY }} + port: ${{ secrets.RACKNERD_PORT || 22 }} + script: | + cd ${{ env.DEPLOY_PATH }} + docker compose up -d --build + docker image prune -f diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9c7305e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:22-alpine AS build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 8080 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b2f54f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + archnest: + build: . + image: archnest:latest + container_name: archnest + restart: unless-stopped + ports: + - "8080:8080" diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..cfb23c8 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,10 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/package-lock.json b/package-lock.json index ae0b1c9..c3e139a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -271,10 +270,31 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, "dependencies": { @@ -793,6 +813,37 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", @@ -1200,7 +1251,6 @@ "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -1211,7 +1261,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1277,7 +1326,6 @@ "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", @@ -1508,7 +1556,6 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1599,7 +1646,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -1893,7 +1939,6 @@ "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2826,7 +2871,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2887,7 +2931,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2897,7 +2940,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2917,7 +2959,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -2970,8 +3011,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -3149,7 +3189,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3266,7 +3305,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3391,7 +3429,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 87c7b28c70c502885eb9da2c9eccb5cf1b613f55 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 14:36:54 +0000 Subject: [PATCH 02/39] Polish Glance page to match mockup Fix System Status "Operational" text to green (was gold italic) and bump size, and switch Shortcuts from a 2x2 grid to a single row of 4 with gold icons to match the blueprint mockup. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF --- src/components/BottomRow.tsx | 8 ++++---- src/components/StatusCards.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/BottomRow.tsx b/src/components/BottomRow.tsx index 1010050..cb93897 100644 --- a/src/components/BottomRow.tsx +++ b/src/components/BottomRow.tsx @@ -82,19 +82,19 @@ export default function BottomRow() {

Shortcuts

-
+
{shortcuts.map((item) => { const Icon = item.icon return ( ) })} diff --git a/src/components/StatusCards.tsx b/src/components/StatusCards.tsx index 27403ec..9b6d32d 100644 --- a/src/components/StatusCards.tsx +++ b/src/components/StatusCards.tsx @@ -22,8 +22,8 @@ export default function StatusCards() {

System Status

-

All Systems

-

Operational

+

All Systems

+

Operational

From 40bd0c9ec7ebbdda911bbf52568b44108fe56fe0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 14:45:00 +0000 Subject: [PATCH 03/39] Widen sidebar and seamlessly blend hero banner into background Sidebar expanded width 140px -> 200px and collapsed 60px -> 64px to match mockup proportions. Hero banner now uses a fixed shorter height with object-fit cover, a bottom mask fade, and a radial vignette so its edges blend into the page background instead of sitting in a bordered box. KPI cards are now semi-transparent so the hero image bleeds through behind them. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF --- design-decisions.md | 4 ++-- src/App.tsx | 42 +++++++++++++++++++++------------- src/components/Sidebar.tsx | 2 +- src/components/StatusCards.tsx | 6 ++--- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/design-decisions.md b/design-decisions.md index 99115f4..9b2fff1 100644 --- a/design-decisions.md +++ b/design-decisions.md @@ -8,8 +8,8 @@ ## Global Rules (Apply to Every Page) ### Sidebar -- **Expanded width**: 100px (not 80px — needs room for labels) -- **Collapsed width**: 60px (icon only) +- **Expanded width**: 200px (matches mockup proportions — needs room for labels) +- **Collapsed width**: 64px (icon only) - **User can manually collapse/expand** via toggle button (not just responsive) - **Main content margin-left** must match sidebar width exactly diff --git a/src/App.tsx b/src/App.tsx index f535b1b..969b4a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import BottomRow from './components/BottomRow' function App() { const [sidebarCollapsed, setSidebarCollapsed] = useState(false) - const sidebarWidth = sidebarCollapsed ? 60 : 140 + const sidebarWidth = sidebarCollapsed ? 64 : 200 return (
@@ -28,21 +28,31 @@ function App() { >
{/* Hero + KPI overlap — KPI bottom aligns with banner bottom */} -
-
- ArchNest Banner { - const target = e.currentTarget - target.style.display = 'none' - target.parentElement!.classList.add('bg-card') - target.parentElement!.style.height = '260px' - }} - /> -
+
+ ArchNest Banner { + const target = e.currentTarget + target.style.display = 'none' + target.parentElement!.classList.add('bg-card') + }} + /> + {/* Side vignette so the rectangular image blends into the page edges */} +
{/* KPI cards positioned so their bottom edge aligns with banner bottom */}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index cd9cb53..e2f5fd4 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -24,7 +24,7 @@ const navItems = [ ] export default function Sidebar({ collapsed, onToggle }: SidebarProps) { - const width = collapsed ? 60 : 140 + const width = collapsed ? 64 : 200 return (