├── .env-development ├── .env-production ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUGS.md │ ├── CHORE.md │ └── FEATURE.md ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ └── make-release.yml ├── .gitignore ├── .idea ├── .gitignore ├── aws.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── turing-pi-ui.iml └── vcs.xml ├── .nginx ├── .htpasswd └── templates │ └── default.conf.template ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── deploy.sh ├── docker-compose.yml ├── package-lock.json ├── package.json ├── playwright.config.ts ├── src ├── app.d.ts ├── app.html ├── index.test.ts ├── lib │ ├── Dashboard.svelte │ ├── Footer.svelte │ ├── Navigation.svelte │ ├── bmc │ │ ├── Nodes.svelte │ │ ├── Server.svelte │ │ ├── TTY.svelte │ │ └── USB.svelte │ ├── stores │ │ ├── nodes.ts │ │ └── server.ts │ └── theme-switcher.ts └── routes │ ├── +error.svelte │ ├── +layout.svelte │ ├── +layout.ts │ ├── +page.svelte │ ├── index.asp │ └── +page.svelte │ ├── pico-bootstrap-grid.css │ └── styles.css ├── svelte.config.js ├── tests └── dashboard.spec.ts ├── tsconfig.json └── vite.config.ts /.env-development: -------------------------------------------------------------------------------- 1 | # Used for proxy connections 2 | BMC_API=http://192.168.1.36 3 | 4 | # Used for frontend connections in development 5 | PUBLIC_SERVICE_API=http://127.0.0.1:8080 6 | PUBLIC_FAKE_API=true 7 | -------------------------------------------------------------------------------- /.env-production: -------------------------------------------------------------------------------- 1 | # Production uses window.location.origin 2 | PUBLIC_SERVICE_API= 3 | PUBLIC_FAKE_API= 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 👥 Contributing 2 | > Guide on how to extend the zero-pi-2 firmware 3 | 4 | ## ℹ️ Overview 5 | 6 | - Project uses github-flow, start by forking the project 7 | - `main` is a stable branch with the latest changes 8 | - Once an issue is created and assigned to a developer, a feature branch is created 9 | - Work is performed in the feature branch then submitted as a Pull Request 10 | 11 | ## 🔀 Flow Operations: 12 | 13 | 1. Fork repository including all branches 14 | 2. Creating a [new issue](https://github.com/PhearZero/turing-pi-ui/issues/new) 15 | 3. Submit PR with completed work to the original Feature Branch associated with the Issue 16 | 17 | ## ⚙️ Building 18 | 19 | See the [README](../README.md) for basic details. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUGS.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug 3 | about: UI is not working as expected 4 | title: 'Fix: ' 5 | labels: 'bug' 6 | --- 7 | 8 | # ℹ Overview 9 | 10 | 13 | 14 | ### 🤖 Turing Pi BMC 15 | 16 | 17 | | Version | USB Mode | USB Node | SD Card | 18 | |---------|----------|----------|-------------------------------------------| 19 | | v0.1.0 | Device | Node1 | Amazon Basics microSDXC Memory Card 128GB | 20 | 21 | ### 💾 Node information 22 | 23 | | Name | OS | SKU | Power | SD Card | 24 | |-------|------------------------|-----------|-------|-------------------------------------------| 25 | | Node1 | Ubuntu 20.04.5 x64 LTS | cm4108000 | On | Amazon Basics microSDXC Memory Card 128GB | 26 | | Node2 | Ubuntu 20.04.5 x64 LTS | cm4108000 | On | Amazon Basics microSDXC Memory Card 128GB | 27 | | Node3 | - | - | Off | | 28 | | Node4 | - | - | Off | | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/CHORE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎨 Chore 3 | about: Regular maintenance task 4 | title: 'Chore: ' 5 | labels: 'enhancement' 6 | --- 7 | 8 | # ℹ Overview 9 | 10 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature 3 | about: Add a new feature to the ui 4 | title: 'Feature: ' 5 | labels: 'enhancement' 6 | --- 7 | 8 | # ℹ Overview 9 | 10 | 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # ℹ Overview 2 | 3 | 6 | 7 | ### 📝 Issues and Pull Requests 8 | 9 | 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 🛠️ Breaking Changes 4 | labels: 5 | - breaking 6 | - title: ✨ Features 7 | labels: 8 | - enhancement 9 | - title: 🐛 Bugs 10 | labels: 11 | - bug 12 | -------------------------------------------------------------------------------- /.github/workflows/make-release.yml: -------------------------------------------------------------------------------- 1 | name: zpi 2 | 3 | on: 4 | pull_request: 5 | branches: ["main"] 6 | push: 7 | tags: 8 | - "v*.*.*" 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [16.x, 18.x, 19.x] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: Install 22 | run: npm ci 23 | - name: Playwright install 24 | run: npx playwright install 25 | - name: Copy ENV 26 | run: cp .env-development .env 27 | - name: Test 28 | run: npm test 29 | - name: Check Shell Script 30 | run: shellcheck ./deploy.sh 31 | release: 32 | needs: test 33 | permissions: 34 | contents: write 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: Use Node.js 39 | uses: actions/setup-node@v3 40 | with: 41 | node-version: 18.x 42 | - name: Install 43 | run: npm ci 44 | - name: Copy ENV 45 | run: cp .env-production .env 46 | - name: Test 47 | run: npm run build 48 | - name: Update Index 49 | run: mv ./build/index.asp.html ./build/index.asp 50 | - name: Tar 51 | run: tar -cvf turing-pi-ui.tar.gz ./build 52 | - name: Release 53 | uses: softprops/action-gh-release@v1 54 | if: startsWith(github.ref, 'refs/tags/') 55 | with: 56 | generate_release_notes: true 57 | files: | 58 | *.tar.gz 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | /build 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | /test-results/ 13 | /playwright-report/ 14 | /playwright/.cache/ 15 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 17 | 18 | 26 | 27 | 30 | 31 | 39 | 40 | 48 | 49 | 57 | 58 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/turing-pi-ui.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nginx/.htpasswd: -------------------------------------------------------------------------------- 1 | admin:$apr1$edItZzjG$7.irUu.aqJ5AGe/Rhkk990 2 | -------------------------------------------------------------------------------- /.nginx/templates/default.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | gzip on; 6 | location / { 7 | try_files $uri $uri/ $uri.html =404; 8 | root /usr/share/nginx/html; 9 | index index.html index.htm; 10 | } 11 | 12 | location /api/bmc { 13 | proxy_pass ${BMC_API}; 14 | proxy_hide_header Content-Type; 15 | add_header Content-Type application/json; 16 | add_header Access-Control-Allow-Origin *; 17 | add_header BMC-Origin ${BMC_API}; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💄 turing-pi-ui 2 | > Web Interface for a Turing Pi 2 BMC 3 | 4 | 5 | ## ℹ️ Overview 6 | 7 | ### 📦 Prerequisites 8 | - [Node.js](https://nodejs.org/en) 9 | - [Turing Pi 2](https://turingpi.com/product/turing-pi-2/) 10 | 11 | This project was created by @PhearZero and is not affiliated with [Turing Machines](). `turing-pi-ui` is a simplifed 12 | interface with a heavy focus on optimizations (304kb bundle size). It leverages the [turing-pi-js](https://www.npmjs.com/package/turing-pi-js) 13 | library, an API client for a Turing Pi 2 BMC. 14 | 15 | ### 📝 Remaining Issues: 16 | - [X] Power Management 17 | - [X] USB Management 18 | - [X] BMC Host Information 19 | - [X] BMC SD Card Information 20 | - [X] Node Information 21 | - [X] UART TTY 22 | - [ ] Allow firmware uploads 23 | 24 | ## 🚀 Quick Install 25 | 26 | Included is a deployment shell script that will upload the assets to the appropriate directory on the BMC 27 | 28 | ```bash 29 | git clone https://github.com/PhearZero/turing-pi-ui 30 | cd turing-pi-ui 31 | 32 | npm install 33 | # Update the target to the BMC 34 | # ./deploy.sh 35 | ./deploy.sh root@192.168.1.X 36 | ``` 37 | 38 | ## 👷 Developing 39 | 40 | ### 🔃 Starting the Proxy 41 | 42 | Included is a simple proxy to fix the headers of the BMC Web API. Ensure that the BMC_API variable is pointing 43 | to a BMC api that is reachable. 44 | 45 | Add the following `.env` file: 46 | ```dotenv 47 | # The location of the BMC API, used for proxy connections 48 | BMC_API=http://192.168.1.36 49 | 50 | # The location of the NGINX proxy, used for frontend connections in development 51 | PUBLIC_SERVICE_API=http://127.0.0.1:8080 52 | ``` 53 | 54 | Run docker services 55 | ```bash 56 | docker compose up 57 | ``` 58 | 59 | ### ⚙️ Running UI Locally 60 | 61 | ```bash 62 | # Install dependencies 63 | npm install 64 | 65 | # Start the server and open the app in a new browser tab 66 | npm run dev -- --open 67 | ``` 68 | 69 | ### 🏗️ Building 70 | 71 | To create a production version of the app: 72 | 73 | ```bash 74 | npm run build 75 | ``` 76 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat << EndOfMessage 4 | _____ _ _ ____ ___ _ _ ____ ____ ___ ___ 5 | |_ _| | | | _ \|_ _| \ | |/ ___| | _ \|_ _| |_ \\ 6 | | | | | | | |_) || || \| | | _ | |_) || | ) | 7 | | | | |_| | _ < | || |\ | |_| | | _ / | | / / 8 | |_| \___/|_| \_|___|_| \_|\____| |_| |___| |___| 9 | 10 | 11 | EndOfMessage 12 | 13 | if [ $# -eq 0 ] 14 | then 15 | echo "Must have a valid ssh ." 16 | echo "./deploy.sh " 17 | exit 1 18 | fi 19 | 20 | 21 | # Build application and create tar 22 | echo "Building bundle..." 23 | [ -f ".env" ] && mv .env .env-disabled 24 | cp .env-production .env 25 | [ ! -d "./node_modules" ] && npm install &> deploy.install.log 26 | npm run build &> deploy.build.log 27 | rm ./build/index.html 28 | mv ./build/index.asp.html ./build/index.asp 29 | 30 | echo 31 | 32 | # Deploy and extract 33 | echo "Upload bundle to $1" 34 | # credit to @srcshelton in Turing Pi Discord, tar to remote folder instead of sending the bundle 35 | tar -cf - ./build | ssh "$1" 'tar -xvf - -C /mnt/var' &> deploy.remote.log 36 | 37 | echo 38 | 39 | now=$(date +%s) 40 | 41 | echo "Backing up service to '/mnt/sdcard/www-$now.tar' and deploying turing-pi-ui" 42 | # credit to @srcshelton in Turing Pi Discord, backup existing www directory to the sdcard 43 | # shellcheck disable=SC2029 44 | ssh "$1" "[ -d /mnt/var/www ] && [ -d /mnt/sdcard ] && tar cf /mnt/sdcard/www-$now.tar -C /mnt/var/ www && rm -rf /mnt/var/www && mv /mnt/var/build /mnt/var/www" &>> deploy.remote.log 45 | 46 | echo 47 | 48 | echo "Cleaning up..." 49 | [ -f ".env-disabled" ] && mv .env-disabled .env 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | proxy: 4 | image: nginx:latest 5 | ports: 6 | - "8080:80" 7 | env_file: 8 | - .env 9 | volumes: 10 | - ./.nginx/templates:/etc/nginx/templates 11 | - ./.nginx/.htpasswd:/etc/nginx/.htpasswd 12 | - ./build:/usr/share/nginx/html 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phearzero/turing-pi-ui", 3 | "version": "0.0.1", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "test": "playwright test", 11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 | "test:unit": "vitest", 14 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 15 | "format": "prettier --plugin-search-dir . --write ." 16 | }, 17 | "devDependencies": { 18 | "@playwright/test": "^1.33.0", 19 | "@sveltejs/adapter-auto": "^2.0.0", 20 | "@sveltejs/adapter-static": "^2.0.2", 21 | "@sveltejs/kit": "^1.5.0", 22 | "@typescript-eslint/eslint-plugin": "^5.45.0", 23 | "@typescript-eslint/parser": "^5.45.0", 24 | "@vite-pwa/sveltekit": "^0.2.1", 25 | "eslint": "^8.28.0", 26 | "eslint-config-prettier": "^8.5.0", 27 | "eslint-plugin-svelte3": "^4.0.0", 28 | "prettier": "^2.8.0", 29 | "prettier-plugin-svelte": "^2.8.1", 30 | "svelte": "^3.54.0", 31 | "svelte-check": "^3.0.1", 32 | "tslib": "^2.4.1", 33 | "typescript": "^5.0.0", 34 | "vite": "^4.2.0", 35 | "vitest": "^0.25.3" 36 | }, 37 | "type": "module", 38 | "dependencies": { 39 | "turing-pi-js": "^1.1.1", 40 | "xterm": "^5.1.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: './tests', 14 | /* Run tests in files in parallel */ 15 | fullyParallel: true, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 2 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: process.env.CI ? 1 : undefined, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: 'html', 24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | use: { 26 | /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // baseURL: 'http://127.0.0.1:3000', 28 | 29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | trace: 'on-first-retry', 31 | }, 32 | 33 | /* Configure projects for major browsers */ 34 | projects: [ 35 | { 36 | name: 'chromium', 37 | use: { ...devices['Desktop Chrome'] }, 38 | }, 39 | 40 | { 41 | name: 'firefox', 42 | use: { ...devices['Desktop Firefox'] }, 43 | }, 44 | 45 | // { 46 | // name: 'webkit', 47 | // use: { ...devices['Desktop Safari'] }, 48 | // }, 49 | 50 | /* Test against mobile viewports. */ 51 | // { 52 | // name: 'Mobile Chrome', 53 | // use: { ...devices['Pixel 5'] }, 54 | // }, 55 | // { 56 | // name: 'Mobile Safari', 57 | // use: { ...devices['iPhone 12'] }, 58 | // }, 59 | 60 | /* Test against branded browsers. */ 61 | // { 62 | // name: 'Microsoft Edge', 63 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 64 | // }, 65 | // { 66 | // name: 'Google Chrome', 67 | // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, 68 | // }, 69 | ], 70 | 71 | /* Run your local dev server before starting the tests */ 72 | webServer: { 73 | command: 'npm run build && npm run preview', 74 | port: 4173, 75 | reuseExistingServer: !process.env.CI, 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 16 | 17 | %sveltekit.head% 18 | 19 | 20 | %sveltekit.body% 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/Dashboard.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 |
10 |

Dashboard

11 |

Settings from the Baseboard Management Controller

12 |
13 |
14 | 15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /src/lib/Footer.svelte: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/lib/Navigation.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 39 |
40 | -------------------------------------------------------------------------------- /src/lib/bmc/Nodes.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 |
20 |
21 |

Nodes

22 |

Configure your compute nodes

23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {#each Object.keys($nodes) as nodeName, i} 38 | 39 | 40 | 41 | 54 | 63 | 72 | 73 | {/each} 74 | 75 |
NameInfoUSB 2.0PowerTTY
{$nodes[nodeName].name}{$nodes[nodeName].info} 42 | {#if typeof $server.usb !== 'undefined'} 43 | 50 | {:else} 51 | 52 | {/if} 53 | 55 | 62 | 64 | 71 |
76 |
77 |
78 |
79 | {#if ttyOpen} 80 | 81 | {/if} 82 | -------------------------------------------------------------------------------- /src/lib/bmc/Server.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 |

Server

9 |
Metadata for BMC Board
10 |
11 |
12 |
13 | 17 |
18 | 22 | 26 | 30 | 31 |
32 |
33 |
34 |
SD Card
35 |
Storage Information
36 |
37 | 38 | 42 | 46 | 50 |
51 |
52 |
53 |
54 |
55 | 59 | 60 |
61 |
62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /src/lib/bmc/TTY.svelte: -------------------------------------------------------------------------------- 1 | 114 | 115 | 116 |
117 |
118 |
119 |
120 |

Connected

121 |

{`${nodeName}@${$server.ip}`}

122 |
123 |
124 | 125 |
126 | 127 |
128 |
129 |
130 |
131 | 141 |
142 |
143 |
144 |
145 |
146 | 147 | 156 |
157 |
158 | 159 | 184 | -------------------------------------------------------------------------------- /src/lib/bmc/USB.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 |
11 |

USB Mode

12 |
Set USB to host or device
13 |
14 |
15 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/lib/stores/nodes.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_FAKE_API } from '$env/static/public'; 2 | import type { Server } from '$lib/stores/server'; 3 | import { server, watch } from '$lib/stores/server'; 4 | import { get, writable } from 'svelte/store'; 5 | import type { NodeInfo, NodePower, USB } from 'turing-pi-js'; 6 | 7 | const FAKER = PUBLIC_FAKE_API === 'true'; 8 | 9 | let client = get(server).client; 10 | type NodeIndex = 0 | 1 | 2 | 3; 11 | type OnOff = 0 | 1; 12 | interface Node { 13 | power: boolean; 14 | usb: boolean; 15 | name: string; 16 | info: string; 17 | } 18 | 19 | interface Nodes { 20 | [k: string]: Node; 21 | } 22 | 23 | export const interval = writable(10000); 24 | 25 | interface KeyUSB extends USB { 26 | [k: string]: NodeIndex | OnOff; 27 | } 28 | 29 | interface KeyNodeInfo extends NodeInfo { 30 | [k: string]: string; 31 | } 32 | 33 | interface KeyNodePower extends NodePower { 34 | [k: string]: OnOff; 35 | } 36 | const createNodesStore = () => { 37 | const nodes = writable({ 38 | node1: { 39 | power: false, 40 | usb: false, 41 | name: 'node1', 42 | info: 'demo' 43 | }, 44 | node2: { 45 | power: false, 46 | usb: false, 47 | name: 'node2', 48 | info: 'demo' 49 | }, 50 | node3: { 51 | power: false, 52 | usb: false, 53 | name: 'node3', 54 | info: 'demo' 55 | }, 56 | node4: { 57 | power: false, 58 | usb: false, 59 | name: 'node4', 60 | info: 'demo' 61 | } 62 | }); 63 | const initialStore = get(server); 64 | 65 | return { 66 | ...nodes, 67 | async update(_server: Server | undefined) { 68 | const _target = typeof _server === 'undefined' ? get(server) : _server; 69 | const isNotUSBChange = initialStore?.usb?.node === _target?.usb?.node && initialStore?.usb?.mode === _target?.usb?.mode 70 | 71 | // Merge server settings 72 | if(typeof _target.usb !== 'undefined'){ 73 | this.merge(_target.usb as KeyUSB, 'usb'); 74 | } 75 | 76 | if (typeof _target.client !== 'undefined' && isNotUSBChange) { 77 | const info = FAKER 78 | ? { 79 | response: [ 80 | { node1: 'Demo Node', node2: 'Demo Node', node3: 'Demo Node', node4: 'Demo Node' } 81 | ] 82 | } 83 | : await client.get('nodeinfo'); 84 | 85 | this.merge(info.response[0] as KeyNodeInfo, 'info'); 86 | 87 | const power = FAKER 88 | ? { response: [{ node1: 0, node2: 2, node3: 0, node4: 0 }] } 89 | : await client.get('power'); 90 | this.merge(power.response[0] as KeyNodePower, 'power'); 91 | } 92 | }, 93 | merge(d: KeyUSB | KeyNodeInfo | KeyNodePower, type: string) { 94 | nodes.update((value) => { 95 | if (type === 'info') { 96 | Object.keys(d).forEach((n) => { 97 | value[n] = { 98 | ...value[n], 99 | info: d[n] as string 100 | }; 101 | }); 102 | } 103 | if (type === 'power') { 104 | Object.keys(d).forEach((n) => { 105 | value[n] = { 106 | ...value[n], 107 | power: d[n] !== 0 108 | }; 109 | }); 110 | } 111 | if (type === 'usb') { 112 | const index = typeof d.node === 'string' ? parseInt(d.node) + 1 : d.node; 113 | Object.keys(value).forEach((n) => { 114 | value[n] = { 115 | ...value[n], 116 | usb: n.match(`node${index + 1}`) !== null 117 | }; 118 | }); 119 | } 120 | return value; 121 | }); 122 | }, 123 | 124 | }; 125 | }; 126 | 127 | export const nodes = createNodesStore(); 128 | 129 | let intervalIndex: unknown; 130 | 131 | const handleInterval = () => { 132 | if (typeof intervalIndex !== 'undefined') { 133 | clearInterval(intervalIndex as number); 134 | intervalIndex = undefined; 135 | } 136 | 137 | if (get(watch)) { 138 | intervalIndex = setInterval(nodes.update, get(interval)); 139 | } 140 | }; 141 | 142 | server.subscribe((value) => { 143 | client = value.client; 144 | nodes.update(value).then(() => { 145 | handleInterval(); 146 | }); 147 | }); 148 | watch.subscribe(() => { 149 | handleInterval(); 150 | }); 151 | -------------------------------------------------------------------------------- /src/lib/stores/server.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SERVICE_API, PUBLIC_FAKE_API } from '$env/static/public'; 2 | import { get, writable } from 'svelte/store'; 3 | import { tpi } from 'turing-pi-js'; 4 | import type {TuringPiInterface, USB, SDCard, Other, PowerQuery, USBQuery, UARTQuery, UARTGetQuery} from 'turing-pi-js'; 5 | 6 | const FAKER = PUBLIC_FAKE_API === 'true' 7 | const DEFAULT_SERVER = 8 | PUBLIC_SERVICE_API !== '' 9 | ? new URL(`${PUBLIC_SERVICE_API}/api/bmc`) 10 | : new URL(`${window.location.origin}/api/bmc`); 11 | 12 | export interface Server { 13 | url: URL; 14 | version?: string; 15 | buildtime?: string; 16 | ip?: string; 17 | mac?: string; 18 | usb?: USB; 19 | sdcard?: SDCard; 20 | client: TuringPiInterface; 21 | } 22 | 23 | let initialized = false; 24 | 25 | function createServerStore() { 26 | const client = tpi(DEFAULT_SERVER); 27 | const _server = writable({ url: DEFAULT_SERVER, client }); 28 | return { 29 | ..._server, 30 | isFaked: FAKER, 31 | async init() { 32 | if (!initialized) { 33 | const other = FAKER ? 34 | // Fake data for build previews 35 | {response:[{ip:window.location.hostname, version: "X.X.X", buildtime: new Date(), mac: "FF:FF:FF:FF:FF"}]} : 36 | await client.get('other'); 37 | 38 | const usb = FAKER ? 39 | // Fake data for build previews 40 | {response:[{node: 0, mode: 0}]} : 41 | await client.get('usb'); 42 | 43 | const sdcard = FAKER ? 44 | // Fake data for build previews 45 | {response:[{total: 10, free: 2, use: 8}]} : 46 | await client.get('sdcard'); 47 | 48 | _server.set({ 49 | usb: usb.response[0] as USB, 50 | sdcard: sdcard.response[0] as SDCard, 51 | url: DEFAULT_SERVER, 52 | client, 53 | ...(other.response[0] as Other) 54 | }); 55 | initialized = true; 56 | } 57 | }, 58 | nodeMap: { 59 | node1: 0, 60 | node2: 1, 61 | node3: 2, 62 | node4: 3 63 | }, 64 | setPower(query: PowerQuery){ 65 | FAKER 66 | ? console.debug('Node Power', query) 67 | : client 68 | .set('power', query, { mode: 'no-cors' }) 69 | .catch(e=>{ 70 | if(e.name === "SyntaxError"){ 71 | console.error('TODO: Fix BMC API HEADERS - BMC API returns text/plain, Recovering from error') 72 | } else { 73 | throw e; 74 | } 75 | }) 76 | }, 77 | setUSB(query: USBQuery){ 78 | function _setServerUSB(_query: USBQuery){ 79 | _server.set({ 80 | ...get(_server), 81 | usb: _query 82 | }); 83 | } 84 | FAKER 85 | ? (()=>{ 86 | console.debug('USB Device', {mode: query.mode, node: query.node}) 87 | _setServerUSB(query) 88 | })() 89 | : client 90 | .set( 91 | 'usb', 92 | query, 93 | { mode: 'no-cors' } 94 | ) 95 | .then(() => { 96 | _setServerUSB(query) 97 | }).catch(e=>{ 98 | if(e.name === "SyntaxError"){ 99 | console.error('TODO: Fix BMC API HEADERS - BMC API returns text/plain, Recovering from error') 100 | } else { 101 | throw e; 102 | } 103 | }); 104 | }, 105 | async getUART(query: UARTGetQuery){ 106 | return FAKER ? {response: [{uart: ""}]} : 107 | client.get('uart', query) 108 | }, 109 | async setUART(query: UARTQuery){ 110 | return FAKER ? console.debug('UART', query) : client.set('uart', query, {mode: 'no-cors'}) 111 | } 112 | }; 113 | } 114 | 115 | export const server = createServerStore(); 116 | 117 | const localWatch = localStorage.watch; 118 | export const watch = writable(typeof localWatch === 'boolean' ? localWatch : false); 119 | watch.subscribe((value) => (localStorage.watch = value)); 120 | -------------------------------------------------------------------------------- /src/lib/theme-switcher.ts: -------------------------------------------------------------------------------- 1 | export const themeSwitcher = { 2 | // Config 3 | _scheme: 'auto' as string | null, 4 | menuTarget: "details[role='list']", 5 | buttonsTarget: 'a[data-theme-switcher]', 6 | buttonAttribute: 'data-theme-switcher', 7 | rootAttribute: 'data-theme', 8 | localStorageKey: 'picoPreferredColorScheme', 9 | 10 | // Init 11 | init() { 12 | this.scheme = this.schemeFromLocalStorage; 13 | this.initSwitchers(); 14 | }, 15 | 16 | // Get color scheme from local storage 17 | get schemeFromLocalStorage() { 18 | if (typeof window.localStorage !== 'undefined') { 19 | if (window.localStorage.getItem(this.localStorageKey) !== null) { 20 | return window.localStorage.getItem(this.localStorageKey); 21 | } 22 | } 23 | return this._scheme; 24 | }, 25 | 26 | // Preferred color scheme 27 | get preferredColorScheme() { 28 | return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 29 | }, 30 | 31 | // Init switchers 32 | async initSwitchers() { 33 | const buttons = document.querySelectorAll(this.buttonsTarget); 34 | buttons.forEach((button) => { 35 | button.addEventListener( 36 | 'click', 37 | (event) => { 38 | event.preventDefault(); 39 | // Set scheme 40 | this.scheme = button.getAttribute(this.buttonAttribute); 41 | // Close dropdown 42 | const dd = document.querySelector(this.menuTarget); 43 | if (dd) { 44 | dd.removeAttribute('open'); 45 | } 46 | }, 47 | false 48 | ); 49 | }); 50 | }, 51 | 52 | // Set scheme 53 | set scheme(scheme) { 54 | if (scheme == 'auto') { 55 | this.preferredColorScheme == 'dark' ? (this._scheme = 'dark') : (this._scheme = 'light'); 56 | } else if (scheme == 'dark' || scheme == 'light') { 57 | this._scheme = scheme; 58 | } 59 | this.applyScheme(); 60 | this.schemeToLocalStorage(); 61 | }, 62 | 63 | // Get scheme 64 | get scheme() { 65 | return this._scheme; 66 | }, 67 | 68 | // Apply scheme 69 | applyScheme() { 70 | const html = document.querySelector('html'); 71 | if (html && this.scheme) { 72 | html.setAttribute(this.rootAttribute, this.scheme); 73 | } 74 | }, 75 | 76 | // Store scheme to local storage 77 | schemeToLocalStorage() { 78 | if (typeof window.localStorage !== 'undefined' && this.scheme) { 79 | window.localStorage.setItem(this.localStorageKey, this.scheme); 80 | } 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |

{$page.status}: {$page.error.message}

6 | Go home 7 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 8 | {#await server.init()} 9 |
10 | {:then s} 11 | 12 |
13 | 14 |
15 |