├── .DS_Store ├── .babelrc ├── .dockerignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── master.yml │ └── push.yml ├── .gitignore ├── .gitlab-ci.yml ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jsLibraryMappings.xml ├── misc.xml ├── modules.xml ├── unraidapi.iml ├── vcs.xml └── workspace.xml ├── Dockerfile ├── DockerfilePI ├── LICENSE ├── README.md ├── api ├── changeArrayStatus.js ├── changeDockerStatus.js ├── changeServerStatus.js ├── changeVMStatus.js ├── createVM.js ├── deleteServer.js ├── editVM.js ├── getServers.js ├── gpuSwap.js ├── login.js ├── mqttDevices.js ├── pciAttach.js ├── proxyImage.ts └── usbAttach.js ├── assets ├── README.md └── style │ ├── app.styl │ └── variables.styl ├── components ├── EditVmCard.vue ├── GpuSwap.vue ├── Logo.vue ├── README.md ├── ServerCard.vue ├── SetupCard.vue ├── UsbDetail.vue ├── VuetifyLogo.vue └── documentation │ └── EditDetail.vue ├── config.json ├── constants └── env.ts ├── jest.config.js ├── layouts ├── README.md ├── default.vue └── error.vue ├── logo.png ├── merge-to-multiple-branches.sh ├── middleware └── README.md ├── mqtt ├── getDockerDetails.ts └── index.ts ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── docs.vue ├── index.vue └── mqtt.vue ├── plugins └── README.md ├── repository.yaml ├── server └── index.js ├── static ├── README.md ├── favicon.ico ├── icon.png ├── iconx64.png ├── sw.js ├── unraid.jpeg └── v.png ├── store └── README.md ├── test-runner.ts ├── test └── unit │ └── utils │ └── Unraid-not-used-test.ts ├── tsconfig.json ├── types ├── json-server.ts └── server.ts ├── unraid-versions ├── 6.12.10 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── 6.12.11 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── 6.12.13 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── 6.12.3 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── 6.12.4 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── 6.12.6 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json └── 7.0.0-beta.2 │ ├── dashboard.html │ ├── dashboardVmDockerDisabled.html │ ├── updateVM.html │ ├── virtualMachines.html │ └── vmObject.json ├── utils ├── Unraid.ts ├── enableDockerFetching.test.ts ├── enableDockerFetching.ts ├── enableVmFetching.test.ts ├── enableVmFetching.ts ├── extractCsrfToken.test.ts ├── extractCsrfToken.ts ├── extractDiskDetails.ts ├── extractServerDetails.test.ts ├── extractServerDetails.ts ├── extractUsbData.test.ts ├── extractUsbData.ts ├── extractUsbDetails.test.ts ├── extractUsbDetails.ts ├── extractValue.ts ├── isEqual.ts ├── logger.ts ├── sanitiseName.ts └── writeTestFile.ts └── vue-shim.d.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": 3 | [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "browsers": ["last 2 versions"] 9 | }, 10 | "debug": true 11 | } 12 | ], 13 | "@babel/preset-typescript" 14 | ], 15 | "plugins": ["@babel/transform-runtime"] 16 | } 17 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nuxt 3 | secure 4 | config 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | // add more generic rulesets here, such as: 4 | "eslint:recommended", 5 | "plugin:vue/recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | root: true, 9 | parser: "vue-eslint-parser", 10 | parserOptions: { parser: "@typescript-eslint/parser" }, 11 | plugins: ["@typescript-eslint"], 12 | rules: { 13 | "prefer-template": "warn", 14 | "vue/max-attributes-per-line": "off", 15 | "vue/html-indent": "off", 16 | "vue/html-self-closing": "off", 17 | "vue/attribute-hyphenation": "off", 18 | "vue/html-closing-bracket-newline": "off", 19 | "vue/require-prop-types": "off", 20 | "vue/html-closing-bracket-spacing": "off", 21 | "vue/singleline-html-element-content-newline": "off", 22 | "vue/mustache-interpolation-spacing": "off", 23 | "vue/multiline-html-element-content-newline": "off", 24 | "vue/name-property-casing": "off", 25 | "vue/order-in-components": "off", 26 | "vue/v-on-style": "off" 27 | 28 | // override/add rules settings here, such as: 29 | // 'vue/no-unused-vars': 'error' 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: Review needed 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Unraid server (please complete the following information):** 27 | - Version [e.g. 6.8.1] 28 | 29 | **Unraidapi docker container (please complete the following information):** 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | 35 | **Diagnostic files from unRAID** 36 | Please upload the diagnostics zip file from your unRAID server. 37 | 38 | > [For Unraid v6.0-rc4 or later] Browse to the Unraid webGui, go to the Tools tab, click on the Diagnostics icon, then click on the Download button (Collect button if v6.0). After the diagnostic data collection is complete, it will save a diagnostics zip file to your computer, to the download location you specify or is configured in your browser. This zipped file is ready to attach to a forum post. It contains a copy of your syslog with DOS friendly line endings, copies of SMART reports for all drives present, copies of your system and share config files, and a technical file describing your array, including all of the content on the Main screen. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Review needed 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: '16.x' 17 | - run: | 18 | git config --global user.email "bokker11@hotmail.com" 19 | git config --global user.name "Norbert Takács" 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v1 22 | 23 | - name: Login to DockerHub 24 | uses: docker/login-action@v1 25 | with: 26 | username: ${{ secrets.DOCKERHUB_USERNAME }} 27 | password: ${{ secrets.DOCKERHUB_TOKEN }} 28 | 29 | - name: Inject slug/short variables 30 | uses: rlespinasse/github-slug-action@v4 31 | 32 | - name: Build and push 33 | uses: docker/build-push-action@v2 34 | with: 35 | push: true 36 | tags: | 37 | bokker/unraidapi-re:${{ env.GITHUB_REF_SLUG }} 38 | bokker/unraidapi-re:latest 39 | 40 | - name: Docker Hub Description 41 | uses: peter-evans/dockerhub-description@v3 42 | with: 43 | username: ${{ secrets.DOCKERHUB_USERNAME }} 44 | password: ${{ secrets.DOCKERHUB_TOKEN }} 45 | repository: bokker/unraidapi-re -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*.*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: '16.x' 17 | - run: | 18 | git config --global user.email "bokker11@hotmail.com" 19 | git config --global user.name "Norbert Takács" 20 | 21 | - name: Set up Docker Buildx 22 | uses: docker/setup-buildx-action@v1 23 | 24 | - name: Login to DockerHub 25 | uses: docker/login-action@v1 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | 30 | - name: Inject slug/short variables 31 | uses: rlespinasse/github-slug-action@v4 32 | 33 | - name: Build and push 34 | uses: docker/build-push-action@v2 35 | with: 36 | push: true 37 | tags: bokker/unraidapi-re:${{ env.GITHUB_REF_SLUG }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /config 11 | /secure 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # intellij 17 | .idea 18 | 19 | .nuxt 20 | .env 21 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - test 4 | - performance 5 | - dast 6 | - deploy 7 | - release 8 | 9 | variables: 10 | EIGHTY_SIX_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:x86-$CI_COMMIT_SHA 11 | ARM_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:arm-$CI_COMMIT_SHA 12 | EIGHTY_SIX_LATEST_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:x86-latest 13 | ARM_LATEST_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:arm-latest 14 | 15 | build-86: 16 | tags: 17 | - machine 18 | - x86 19 | stage: build 20 | script: 21 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 22 | - docker build -t $EIGHTY_SIX_TAG . 23 | - docker push $EIGHTY_SIX_TAG 24 | - docker tag $EIGHTY_SIX_TAG $EIGHTY_SIX_LATEST_TAG 25 | - docker push $EIGHTY_SIX_LATEST_TAG 26 | 27 | build-arm: 28 | tags: 29 | - machine 30 | - arm 31 | stage: build 32 | script: 33 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 34 | - docker build -f DockerfilePI -t $ARM_TAG . 35 | - docker push $ARM_TAG 36 | - docker tag $ARM_TAG $ARM_LATEST_TAG 37 | - docker push $ARM_LATEST_TAG 38 | allow_failure: true 39 | 40 | deploy-dockerhub-beta-86: 41 | tags: 42 | - machine 43 | stage: deploy 44 | only: 45 | - master 46 | script: 47 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 48 | - docker pull $EIGHTY_SIX_TAG 49 | - docker tag $EIGHTY_SIX_TAG electricbrainuk/unraidapi:beta 50 | - docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS 51 | - docker push electricbrainuk/unraidapi:beta 52 | 53 | deploy-dockerhub-86: 54 | tags: 55 | - machine 56 | stage: release 57 | when: manual 58 | only: 59 | - master 60 | script: 61 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 62 | - docker pull $EIGHTY_SIX_TAG 63 | - docker tag $EIGHTY_SIX_TAG electricbrainuk/unraidapi:latest 64 | - docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS 65 | - docker push electricbrainuk/unraidapi:latest 66 | 67 | 68 | include: 69 | - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml 70 | - template: Jobs/Code-Intelligence.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml 71 | - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml 72 | - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml 73 | - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml 74 | - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml 75 | - template: Security/License-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml 76 | - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml 77 | - template: Security/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml 78 | 79 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 19 | 20 | 21 | 25 | 26 | 27 | 34 | 35 | 42 | 43 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/unraidapi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | ENV NODE_ENV=production 4 | ENV HOST 0.0.0.0 5 | ENV PORT 80 6 | ENV NODE_OPTIONS="--max_old_space_size=4096" 7 | 8 | ENV APP_ROOT /app 9 | 10 | RUN mkdir -p ${APP_ROOT} 11 | COPY . ${APP_ROOT} 12 | WORKDIR ${APP_ROOT} 13 | # Expose the app port 14 | EXPOSE 80 15 | 16 | RUN npm install --omit=dev --legacy-peer-deps 17 | RUN npm run build 18 | CMD ["npm", "start"] 19 | -------------------------------------------------------------------------------- /DockerfilePI: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV NODE_ENV=production 4 | ENV HOST 0.0.0.0 5 | ENV PORT 80 6 | ENV NODE_OPTIONS="--max_old_space_size=256" 7 | 8 | ENV APP_ROOT /app 9 | 10 | RUN mkdir -p ${APP_ROOT} 11 | COPY . ${APP_ROOT} 12 | WORKDIR ${APP_ROOT} 13 | # Expose the app port 14 | EXPOSE 80 15 | 16 | RUN npm install 17 | RUN npm run build 18 | CMD ["npm", "start"] 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnraidAPI-RE 2 | 3 |

4 | 5 |

6 |

7 | Icon made by Freepik 8 |

9 | 10 | 11 |

12 | 13 |

14 | 15 | > A new UI and API for controlling multiple unraid instances and connecting them to the Home Assistant 16 | > Fork of the original project which is abandoned ElectricBrainUK/UnraidAPI 17 | 18 | [Open an issue](https://github.com/bokker/UnraidAPI-RE/issues/new?assignees=&labels=Review+needed&template=bug_report.md&title=) 19 | 20 | The tags follow the unraid major releases and should work for minor also: 21 | 22 | ```docker pull bokker/unraidapi-re:6.12``` for unraid 6.12 23 | 24 | ## External links 25 | * [Unraid forum support topic](https://forums.unraid.net/topic/141974-support-fork-unraid-api-re/) 26 | * [Dockerhub](https://hub.docker.com/r/bokker/unraidapi-re/) 27 | 28 | # Install 29 | ## Community Applications unraid 30 | * Install CA: [Youtube guide installing CA on unraid](https://www.youtube.com/watch?v=su2miwZNuaU) & [CA support unraid forums](https://forums.unraid.net/topic/38582-plug-in-community-applications/) 31 | * Go to the `apps` tab and search for `unraid-api` and install it. 32 | 33 | ## Home Assistant AddOn 34 | * Add the following custom repository: https://github.com/BoKKeR/unraidapi-re 35 | * Build the Addon 36 | * Fill in the config section 37 | * Start 38 | 39 | ### Env variables 40 | | Name | Type | Default | Description 41 | | ---- | ---- | ------- | ----------- 42 | | MQTTBroker | string | **Required if enabled** | Your broker ip-address or domain e.g. `hassio` 43 | | MQTTPort | number | **Required if enabled** | 1883 (Plain) / 8883 (SSL on hassio) 44 | | MQTTUser | string | none | MQTT username 45 | | MQTTPass | string | none | MQTT password 46 | | MQTTBaseTopic | string | homeassistant | The base topic for all MQTT publishes 47 | | MQTTSecure | boolean | false | For MQTT Over SSL set to `true` 48 | | MQTTSelfSigned | boolean | false | If you are using a self signed certificate set to `true` 49 | | MQTTRefreshRate | number | 60 | Time in seconds to poll for updates 50 | | MQTTCacheTime | number | 60 | Time in minutes after which all entities will be updated in MQTT 51 | | LOG_LEVEL | string | info | info, debug, error, warn 52 | | KeyStorage | string | config | Where to store the secure keys. If left blank the keys are kept in memory and will be destroyed each time the container is updated. Set to config to have the data persist 53 | | WRITE_HTML_OUTPUT | boolean | false | Writes the html files it scrapes to config/html_output 54 | | Docker host port | number | 3005 | Default web-UI port. Container 80:3005 host 55 | 56 | 57 | 58 | # Home Assistant Integration 59 | ## Automatic 60 | Check out the HA docs on how to set up discovery for MQTT here: 61 | https://www.home-assistant.io/docs/mqtt/discovery/ 62 | 63 | Use the env variable section to set up the MQTT client and connect to your MQTT broker. If auto discovery is enabled in home assistant the following will be created: 64 | - An entity for each of your servers 65 | - (sensor) Monitor server status 66 | - (switch) On/Off switch allows you to start stop array 67 | - An entity for each of your VMs 68 | - (switch) On/Off toggle VM state 69 | - (switch) A seperate entity with a switch to attach / detach any usbs to that vm 70 | - (sensor) Whether or not a particular usb device is connected to the machine (can be used to automate hotplugging e.g. when connected toggle the usb switch off and on again) 71 | - An entity for each of your dockers 72 | - (switch) On/Off toggle Docker state 73 | 74 | You will end up having entities like these: 75 | 76 | * binary_sensor.unraid_server **(Server info in attributes)** 77 | * binary_sensor.unraid_vm_VMNAME_usb_USBDEVICE 78 | * sensor.unraid_vm_VMNAME_status **(VM stats in attributes)** 79 | * switch.unraid_array 80 | * switch.unraid_docker_DOCKERNAME **(Docker info in attributes)** 81 | * switch.unraid_vm_VMNAME **(VM info in attributes)** 82 | 83 | ## Manual 84 | Manual Config Example: 85 | The server and VM names are as they are in MQTT (spaces are underscores and all lower case) 86 | The **payload options** are **started, stopped, paused, restart, kill, hibernate** 87 | 88 | ``` 89 | - platform: mqtt 90 | 91 | command_topic: "homeassistant/servername/vmname/state" 92 | 93 | payload_on: "started" 94 | 95 | payload_off: "stopped" 96 | ``` 97 | 98 | When connecting the unraid api to an mqtt broker config details for all the various api functions are posted under the various homeassistant entity types. For example under **homeassistant/switch/server/vm/config**. 99 | 100 | 101 | 106 | 112 | Star History Chart 116 | 117 | -------------------------------------------------------------------------------- /api/changeArrayStatus.js: -------------------------------------------------------------------------------- 1 | import { changeArrayState, getCSRFToken } from "../utils/Unraid"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let response = {}; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let token = await getCSRFToken(data.server, data.auth); 15 | response.message = await changeArrayState( 16 | data.action, 17 | data.server, 18 | data.auth, 19 | token 20 | ); 21 | response.status = 200; 22 | res.send(response); 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /api/changeDockerStatus.js: -------------------------------------------------------------------------------- 1 | import { changeDockerState, getCSRFToken } from "../utils/Unraid"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let response = {}; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let token = await getCSRFToken(data.server, data.auth); 15 | response.message = await changeDockerState( 16 | data.id, 17 | data.action, 18 | data.server, 19 | data.auth, 20 | token 21 | ); 22 | response.status = 200; 23 | res.send(response); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /api/changeServerStatus.js: -------------------------------------------------------------------------------- 1 | import { changeServerState, getCSRFToken } from "../utils/Unraid"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let response = {}; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let token = await getCSRFToken(data.server, data.auth); 15 | response.message = await changeServerState( 16 | data.action, 17 | data.server, 18 | data.auth, 19 | token 20 | ); 21 | response.status = 200; 22 | res.send(response); 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /api/changeVMStatus.js: -------------------------------------------------------------------------------- 1 | import { changeVMState, getCSRFToken } from "../utils/Unraid"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let response = {}; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let token = await getCSRFToken(data.server, data.auth); 15 | response.message = await changeVMState( 16 | data.id, 17 | data.action, 18 | data.server, 19 | data.auth, 20 | token 21 | ); 22 | response.status = 200; 23 | res.send(response); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /api/createVM.js: -------------------------------------------------------------------------------- 1 | import { changeVMState, getCSRFToken, requestChange } from "../utils/Unraid"; 2 | 3 | const defaultVM = { 4 | gpus: [ 5 | { 6 | id: "vnc", 7 | model: "qxl", 8 | keymap: "en-us" 9 | } 10 | ] 11 | }; 12 | 13 | export default function(req, res, next) { 14 | let body = []; 15 | let data; 16 | req 17 | .on("data", (chunk) => { 18 | body.push(chunk); 19 | }) 20 | .on("end", async () => { 21 | data = JSON.parse(Buffer.concat(body).toString()); 22 | if (data) { 23 | let response = {}; 24 | response.message = await editVM(data); 25 | response.status = 200; 26 | res.send(response); 27 | } 28 | }); 29 | } 30 | 31 | async function editVM(data) { 32 | let defaultVMObject = {}; 33 | defaultVMObject.edit = Object.assign({}, defaultVM); 34 | defaultVMObject.edit.domain_uuid = /^[0-9A-Fa-f]{8}(?:\-[0-9A-Fa-f]{4}){3}\-[0-9A-Fa-f]{12}$/; //todo generate a mac 35 | Object.keys(data.edit).forEach((key) => { 36 | defaultVMObject.edit[key] = data.edit[key]; 37 | }); 38 | 39 | let token = await getCSRFToken(data.server, data.auth); 40 | 41 | await changeVMState(data.id, "domain-stop", data.server, data.auth, token); 42 | let result = await requestChange( 43 | data.server, 44 | data.id, 45 | data.auth, 46 | defaultVMObject.edit, 47 | create 48 | ); 49 | await changeVMState(data.id, "domain-start", data.server, data.auth, token); 50 | return result; 51 | } 52 | -------------------------------------------------------------------------------- /api/deleteServer.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let response = {}; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | const ip = Buffer.concat(body).toString(); 13 | 14 | for (let i = 0; i < 180; i++) { 15 | setTimeout(() => { 16 | deleteIP(ip); 17 | }, 1000 * i); 18 | } 19 | 20 | response.status = 200; 21 | res.send(response); 22 | }); 23 | } 24 | 25 | function deleteIP(ip) { 26 | const rawdata = fs.readFileSync("config/servers.json"); 27 | let servers = JSON.parse(rawdata); 28 | 29 | servers[ip] = undefined; 30 | 31 | fs.writeFileSync("config/servers.json", JSON.stringify(servers, null, 2)); 32 | } 33 | -------------------------------------------------------------------------------- /api/editVM.js: -------------------------------------------------------------------------------- 1 | import { 2 | changeVMState, 3 | gatherDetailsFromEditVM, 4 | getCSRFToken, 5 | requestChange 6 | } from "../utils/Unraid"; 7 | 8 | export default function(req, res, next) { 9 | let body = []; 10 | let data; 11 | req 12 | .on("data", (chunk) => { 13 | body.push(chunk); 14 | }) 15 | .on("end", async () => { 16 | data = JSON.parse(Buffer.concat(body).toString()); 17 | if (data) { 18 | let response = {}; 19 | response.message = await editVM(data); 20 | response.status = 200; 21 | res.send(response); 22 | } 23 | }); 24 | } 25 | 26 | async function editVM(data) { 27 | let existingVMObject = await gatherDetailsFromEditVM( 28 | data.server, 29 | data.id, 30 | undefined, 31 | data.auth 32 | ); 33 | Object.keys(data.edit).forEach((key) => { 34 | existingVMObject.edit[key] = data.edit[key]; 35 | }); 36 | 37 | let token = await getCSRFToken(data.server, data.auth); 38 | 39 | await changeVMState(data.id, "domain-stop", data.server, data.auth, token); 40 | let result = await requestChange( 41 | data.server, 42 | data.id, 43 | data.auth, 44 | existingVMObject.edit 45 | ); 46 | await changeVMState(data.id, "domain-start", data.server, data.auth, token); 47 | return result; 48 | } 49 | -------------------------------------------------------------------------------- /api/getServers.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { getUnraidDetails } from "../utils/Unraid"; 3 | 4 | export default function(req, res, next) { 5 | let servers = {}; 6 | try { 7 | servers = JSON.parse(fs.readFileSync("config/servers.json")); 8 | } catch (e) { 9 | console.log("Failed to retrieve config file, creating new."); 10 | if (!fs.existsSync("config/")) { 11 | fs.mkdirSync("config/"); 12 | } 13 | fs.writeFileSync("config/servers.json", JSON.stringify(servers, null, 2)); 14 | } 15 | if ( 16 | (!req.headers.authorization || 17 | Object.keys(req.headers.authorization).length < 18 | Object.keys(servers).length) && 19 | process.env.KeyStorage !== "config" 20 | ) { 21 | let response = {}; 22 | Object.keys(servers).forEach((ip) => { 23 | response[ip] = true; 24 | }); 25 | res.send({ servers: response }); 26 | return; 27 | } 28 | let response = {}; 29 | response.servers = servers; 30 | 31 | if ( 32 | process.env.KeyStorage === "config" && 33 | (!req.headers.authorization || req.headers.authorization.length <= 2) 34 | ) { 35 | req.headers.authorization = fs.readFileSync( 36 | `${ 37 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 38 | }mqttKeys` 39 | ); 40 | } 41 | 42 | getUnraidDetails(response.servers, JSON.parse(req.headers.authorization)); 43 | response.status = 200; 44 | res.send(response); 45 | } 46 | -------------------------------------------------------------------------------- /api/gpuSwap.js: -------------------------------------------------------------------------------- 1 | import { 2 | addPCICheck, 3 | changeVMState, 4 | flipPCICheck, 5 | gatherDetailsFromEditVM, 6 | getCSRFToken, 7 | removePCICheck, 8 | requestChange 9 | } from "../utils/Unraid"; 10 | import fs from "fs"; 11 | 12 | export default function(req, res, next) { 13 | let body = []; 14 | let data; 15 | req 16 | .on("data", (chunk) => { 17 | body.push(chunk); 18 | }) 19 | .on("end", async () => { 20 | data = JSON.parse(Buffer.concat(body).toString()); 21 | if (data) { 22 | let response = {}; 23 | response.message = await gpuSwap(data); 24 | response.status = 200; 25 | res.send(response); 26 | } 27 | }); 28 | } 29 | 30 | async function gpuSwap(data) { 31 | let vm1 = await gatherDetailsFromEditVM( 32 | data.server, 33 | data.id1, 34 | undefined, 35 | data.auth 36 | ); 37 | let vm2 = await gatherDetailsFromEditVM( 38 | data.server, 39 | data.id2, 40 | undefined, 41 | data.auth 42 | ); 43 | 44 | let token = await getCSRFToken(data.server, data.auth); 45 | 46 | let vm1PrimaryGPU = vm1.edit.pcis.filter( 47 | (device) => device.gpu && device.checked 48 | )[0]; 49 | let vm2PrimaryGPU = vm2.edit.pcis.filter( 50 | (device) => device.gpu && device.checked 51 | )[0]; 52 | 53 | removePCICheck(vm1.edit, vm1PrimaryGPU.id); 54 | removePCICheck(vm2.edit, vm2PrimaryGPU.id); 55 | addPCICheck(vm1.edit, vm2PrimaryGPU.id); 56 | addPCICheck(vm2.edit, vm1PrimaryGPU.id); 57 | 58 | let temp = Object.assign( 59 | "", 60 | vm1.edit.pcis.filter((device) => device.id === vm2PrimaryGPU.id)[0].bios 61 | ); 62 | vm1.edit.pcis.filter( 63 | (device) => device.id === vm2PrimaryGPU.id 64 | )[0].bios = Object.assign( 65 | "", 66 | vm2.edit.pcis.filter((device) => device.id === vm1PrimaryGPU.id)[0].bios 67 | ); 68 | vm2.edit.pcis.filter( 69 | (device) => device.id === vm1PrimaryGPU.id 70 | )[0].bios = temp; 71 | 72 | if (data.pciIds) { 73 | data.pciIds.forEach((pciId) => { 74 | flipPCICheck(vm1.edit, pciId); 75 | flipPCICheck(vm2.edit, pciId); 76 | }); 77 | } 78 | 79 | await Promise.all([ 80 | changeVMState(data.id1, "domain-stop", data.server, data.auth, token), 81 | changeVMState(data.id2, "domain-stop", data.server, data.auth, token) 82 | ]); 83 | 84 | let result1 = await requestChange(data.server, data.id1, data.auth, vm1.edit); 85 | let result2 = await requestChange(data.server, data.id2, data.auth, vm2.edit); 86 | 87 | await Promise.all([ 88 | changeVMState(data.id1, "domain-start", data.server, data.auth, token), 89 | changeVMState(data.id2, "domain-start", data.server, data.auth, token) 90 | ]); 91 | 92 | return { vm1: result1, vm2: result2 }; 93 | } 94 | -------------------------------------------------------------------------------- /api/login.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let data; 6 | req 7 | .on("data", (chunk) => { 8 | body.push(chunk); 9 | }) 10 | .on("end", async () => { 11 | data = JSON.parse(Buffer.concat(body).toString()); 12 | if (data) { 13 | let response = {}; 14 | response = { ...response, ...(await connectToServer(data)) }; 15 | response.status = 200; 16 | res.send(response); 17 | } 18 | }); 19 | } 20 | 21 | async function connectToServer(data) { 22 | let response = {}; 23 | let servers = {}; 24 | try { 25 | if (!fs.existsSync("config/")) { 26 | fs.mkdirSync("config/"); 27 | } else { 28 | let rawdata = fs.readFileSync("config/servers.json"); 29 | servers = JSON.parse(rawdata); 30 | } 31 | if ( 32 | !fs.existsSync( 33 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 34 | ) 35 | ) { 36 | fs.mkdirSync( 37 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 38 | ); 39 | } 40 | if ( 41 | !fs.existsSync( 42 | `${ 43 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 44 | }mqttKeys` 45 | ) 46 | ) { 47 | fs.writeFileSync( 48 | process.env.KeyStorage 49 | ? `${process.env.KeyStorage}/` 50 | : "secure/mqttKeys", 51 | {} 52 | ); 53 | } 54 | } catch (e) { 55 | // console.log(e); 56 | } finally { 57 | let keys = {}; 58 | try { 59 | keys = JSON.parse( 60 | fs.readFileSync( 61 | `${ 62 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 63 | }mqttKeys` 64 | ) 65 | ); 66 | } catch (e) { 67 | // console.log(e); 68 | } finally { 69 | if (data.ip) { 70 | servers[data.ip] = {}; 71 | keys[data.ip] = data.authToken; 72 | fs.writeFileSync( 73 | `${ 74 | process.env.KeyStorage ? `${process.env.KeyStorage}/` : "secure/" 75 | }mqttKeys`, 76 | JSON.stringify(keys, null, 2) 77 | ); 78 | } 79 | 80 | fs.writeFileSync("config/servers.json", JSON.stringify(servers, null, 2)); 81 | response.message = "Connected"; 82 | } 83 | } 84 | return response; 85 | } 86 | -------------------------------------------------------------------------------- /api/mqttDevices.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export default function(req, res, next) { 4 | let body = []; 5 | let data; 6 | req 7 | .on("data", (chunk) => { 8 | body.push(chunk); 9 | }) 10 | .on("end", async () => { 11 | try { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let response = {}; 15 | storeDevices(data); 16 | response.message = "Success"; 17 | response.status = 200; 18 | res.send(response); 19 | } 20 | } catch (e) { 21 | try { 22 | res.send(fs.readFileSync("config/mqttDisabledDevices.json")); 23 | } catch (e) { 24 | fs.writeFileSync( 25 | "config/mqttDisabledDevices.json", 26 | JSON.stringify([]) 27 | ); 28 | } 29 | } 30 | }); 31 | } 32 | 33 | function storeDevices(data) { 34 | fs.writeFileSync( 35 | "config/mqttDisabledDevices.json", 36 | JSON.stringify(data, null, 2) 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /api/pciAttach.js: -------------------------------------------------------------------------------- 1 | import { 2 | addPCICheck, 3 | changeVMState, 4 | gatherDetailsFromEditVM, 5 | getCSRFToken, 6 | removePCICheck, 7 | requestChange 8 | } from "../utils/Unraid"; 9 | import fs from "fs"; 10 | 11 | export default function(req, res, next) { 12 | let body = []; 13 | let data; 14 | req 15 | .on("data", (chunk) => { 16 | body.push(chunk); 17 | }) 18 | .on("end", async () => { 19 | data = JSON.parse(Buffer.concat(body).toString()); 20 | if (data) { 21 | let response = {}; 22 | if (!data.option) { 23 | response.message = await attachPCI(data); 24 | } else if (data.option === "detach") { 25 | response.message = await detachPCI(data); 26 | } 27 | response.status = 200; 28 | res.send(response); 29 | } 30 | }); 31 | } 32 | 33 | async function attachPCI(data) { 34 | if (data.pciId && !data.pciIds) { 35 | data.pciIds = [data.pciId]; 36 | } 37 | 38 | let vmObject = await gatherDetailsFromEditVM( 39 | data.server, 40 | data.id, 41 | undefined, 42 | data.auth 43 | ); 44 | let rawdata = fs.readFileSync("config/servers.json"); 45 | let servers = JSON.parse(rawdata); 46 | let attached = []; 47 | 48 | data.pciIds.forEach((pciId) => { 49 | Object.keys(servers[data.server].vm.details).forEach((vmId) => { 50 | let vm = servers[data.server].vm.details[vmId]; 51 | if (vm.edit && vm.edit.pcis && vm.status === "started") { 52 | vm.edit.pcis.forEach((pciDevice) => { 53 | if ( 54 | pciDevice.id.split(".")[0] === pciId.split(".")[0] && 55 | vmId !== data.id && 56 | pciDevice.checked 57 | ) { 58 | attached.push({ pciId: pciDevice.id, vmId, vm }); 59 | } 60 | }); 61 | } 62 | }); 63 | addPCICheck(vmObject.edit, pciId); 64 | }); 65 | 66 | let token = await getCSRFToken(data.server, data.auth); 67 | let stopped = {}; 68 | if (attached) { 69 | for (let i = 0; i < attached.length; i++) { 70 | let vmWithPciDevice = attached[i]; 71 | removePCICheck(vmWithPciDevice.vm.edit, vmWithPciDevice.pciId); 72 | if (!stopped[vmWithPciDevice.vmId]) { 73 | await changeVMState( 74 | vmWithPciDevice.vmId, 75 | "domain-stop", 76 | data.server, 77 | data.auth, 78 | token 79 | ); 80 | } 81 | await requestChange( 82 | data.server, 83 | vmWithPciDevice.vmId, 84 | servers[data.server].authToken, 85 | vmWithPciDevice.vm.edit 86 | ); 87 | stopped[vmWithPciDevice.vmId] = true; 88 | } 89 | } 90 | 91 | await Promise.all( 92 | Object.keys(stopped).map((stoppedVMId) => 93 | changeVMState(stoppedVMId, "domain-start", data.server, data.auth, token) 94 | ) 95 | ); 96 | 97 | await changeVMState(data.id, "domain-stop", data.server, data.auth, token); 98 | let result = await requestChange( 99 | data.server, 100 | data.id, 101 | data.auth, 102 | vmObject.edit 103 | ); 104 | await changeVMState(data.id, "domain-start", data.server, data.auth, token); 105 | return result; 106 | } 107 | 108 | async function detachPCI(data) { 109 | if (data.pciId && !data.pciIds) { 110 | data.pciIds = [data.pciId]; 111 | } 112 | 113 | let vmObject = await gatherDetailsFromEditVM( 114 | data.server, 115 | data.id, 116 | undefined, 117 | data.auth 118 | ); 119 | 120 | data.pciIds.forEach((pciId) => { 121 | removePCICheck(vmObject.edit, pciId); 122 | }); 123 | 124 | let token = await getCSRFToken(data.server, data.auth); 125 | await changeVMState(data.id, "domain-stop", data.server, data.auth, token); 126 | let result = await requestChange( 127 | data.server, 128 | data.id, 129 | data.auth, 130 | vmObject.edit 131 | ); 132 | await changeVMState(data.id, "domain-start", data.server, data.auth, token); 133 | return result; 134 | } 135 | -------------------------------------------------------------------------------- /api/proxyImage.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { getImage } from "../utils/Unraid"; 3 | 4 | export default function(req, res, next) { 5 | let servers = {}; 6 | try { 7 | servers = JSON.parse(fs.readFileSync("config/servers.json", "utf8")); 8 | } catch (e) { 9 | console.log("Failed to retrieve config file, creating new."); 10 | if (!fs.existsSync("config/")) { 11 | fs.mkdirSync("config/"); 12 | } 13 | fs.writeFileSync("config/servers.json", JSON.stringify(servers, null, 2)); 14 | } 15 | getImage(servers, res, req.path); 16 | } 17 | -------------------------------------------------------------------------------- /api/usbAttach.js: -------------------------------------------------------------------------------- 1 | import { gatherDetailsFromEditVM, requestChange } from "../utils/Unraid"; 2 | import fs from "fs"; 3 | 4 | export default function(req, res, next) { 5 | let body = []; 6 | let data; 7 | req 8 | .on("data", (chunk) => { 9 | body.push(chunk); 10 | }) 11 | .on("end", async () => { 12 | data = JSON.parse(Buffer.concat(body).toString()); 13 | if (data) { 14 | let response = {}; 15 | if (!data.option) { 16 | response.message = await attachUSB(data); 17 | } else if (data.option === "reattach") { 18 | response.message = await reattachUSB(data); 19 | } else if (data.option === "detach") { 20 | response.message = await detachUSB(data); 21 | } 22 | response.status = 200; 23 | res.send(response); 24 | } 25 | }); 26 | } 27 | 28 | export async function attachUSB(data) { 29 | let vmObject = await gatherDetailsFromEditVM( 30 | data.server, 31 | data.id, 32 | undefined, 33 | data.auth 34 | ); 35 | let rawdata = fs.readFileSync("config/servers.json"); 36 | let servers = JSON.parse(rawdata); 37 | let attached = {}; 38 | 39 | Object.keys(servers[data.server].vm.details).forEach((vmId) => { 40 | let vm = servers[data.server].vm.details[vmId]; 41 | if (vm.edit && vm.edit.usbs && vm.status === "started") { 42 | vm.edit.usbs.forEach((usbDevice) => { 43 | if ( 44 | usbDevice.id === data.usbId && 45 | vmId !== data.id && 46 | usbDevice.checked 47 | ) { 48 | attached = { usbId: usbDevice.id, vmId, vm }; 49 | } 50 | }); 51 | } 52 | }); 53 | 54 | if (attached.vm) { 55 | removeUSBCheck(attached.vm.edit, attached.usbId); 56 | await requestChange( 57 | data.server, 58 | attached.vmId, 59 | data.auth, 60 | attached.vm.edit 61 | ); 62 | } 63 | 64 | addUSBCheck(vmObject.edit, data.usbId); 65 | return requestChange(data.server, data.id, data.auth, vmObject.edit); 66 | } 67 | 68 | async function reattachUSB(data) { 69 | let vmObject = await gatherDetailsFromEditVM( 70 | data.server, 71 | data.id, 72 | undefined, 73 | data.auth 74 | ); 75 | 76 | removeUSBCheck(vmObject.edit, data.usbId); 77 | await requestChange(data.server, data.id, data.auth, vmObject.edit); 78 | addUSBCheck(vmObject.edit, data.usbId); 79 | return requestChange(data.server, data.id, data.auth, vmObject.edit); 80 | } 81 | 82 | export async function detachUSB(data) { 83 | let vmObject = await gatherDetailsFromEditVM( 84 | data.server, 85 | data.id, 86 | undefined, 87 | data.auth 88 | ); 89 | 90 | removeUSBCheck(vmObject.edit, data.usbId); 91 | return requestChange(data.server, data.id, data.auth, vmObject.edit); 92 | } 93 | 94 | function removeUSBCheck(details, id) { 95 | details.usbs.filter((usbDevice) => usbDevice.id === id)[0].checked = false; 96 | } 97 | 98 | function addUSBCheck(details, id) { 99 | details.usbs.filter((usbDevice) => usbDevice.id === id)[0].checked = true; 100 | } 101 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /assets/style/app.styl: -------------------------------------------------------------------------------- 1 | // Import Vuetify styling 2 | @require '~vuetify/src/stylus/app.styl' 3 | -------------------------------------------------------------------------------- /assets/style/variables.styl: -------------------------------------------------------------------------------- 1 | @require '~vuetify/src/stylus/settings/_variables.styl' 2 | -------------------------------------------------------------------------------- /components/EditVmCard.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /components/GpuSwap.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /components/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /components/SetupCard.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /components/UsbDetail.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 199 | 200 | -------------------------------------------------------------------------------- /components/VuetifyLogo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /components/documentation/EditDetail.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unraid API-RE", 3 | "version": "2.0", 4 | "slug": "unraid_api_re", 5 | "description": "An Unraid REST/MQTT Bridge for HA and other IOT platforms", 6 | "arch": ["armhf", "armv7", "aarch64", "amd64", "i386"], 7 | "startup": "before", 8 | "boot": "auto", 9 | "webui": "http://[HOST]:[PORT:80]", 10 | "options": { 11 | "MQTTBroker": null, 12 | "MQTTPort": 1883, 13 | "MQTTUser": null, 14 | "MQTTPass": null, 15 | "MQTTBaseTopic": "homeassistant", 16 | "MQTTSecure": false, 17 | "MQTTSelfSigned": false, 18 | "MQTTRefreshRate": null, 19 | "MQTTCacheTime": null, 20 | "KeyStorage": "config" 21 | }, 22 | "schema": { 23 | "MQTTBroker": "str?", 24 | "MQTTPort": "int?", 25 | "MQTTUser": "str?", 26 | "MQTTPass": "str?", 27 | "MQTTBaseTopic": "str?", 28 | "MQTTSecure": "bool?", 29 | "MQTTSelfSigned": "bool?", 30 | "MQTTRefreshRate": "int?", 31 | "MQTTCacheTime": "int?", 32 | "KeyStorage": "str?" 33 | }, 34 | "ports": { 35 | "80/tcp": 3005 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /constants/env.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | MQTTBroker: process.env.MQTTBroker, 3 | RetainMessages: process.env.RetainMessages === "true", 4 | MQTTBaseTopic: process.env.MQTTBaseTopic, 5 | MQTTRefreshRate: process.env.MQTTRefreshRate 6 | ? parseInt(process.env.MQTTRefreshRate) 7 | : 60, 8 | MQTTCacheTime: process.env.MQTTCacheTime 9 | ? parseInt(process.env.MQTTCacheTime) 10 | : 60, 11 | KeyStorage: process.env.KeyStorage || "secure", 12 | 13 | MQTTConnection: { 14 | username: process.env.MQTTUser, // MQTT username 15 | password: process.env.MQTTPass, // MQTT password 16 | port: process.env.MQTTPort, // MQTT port 17 | host: process.env.MQTTBroker, // MQTT broker host 18 | rejectUnauthorized: process.env.MQTTSelfSigned !== "true", // Determine if self-signed certificates are rejected 19 | secure: process.env.MQTTSecure === "true" 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | moduleFileExtensions: ["js", "jsx", "json", "vue", "ts"], 4 | transform: { 5 | "^.+\\.vue$": "vue-jest", 6 | ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": 7 | "jest-transform-stub", 8 | "^.+\\.(js|jsx)?$": "babel-jest", 9 | "node_modules/variables/.+\\.(j|t)sx?$": "ts-jest", 10 | "^.+\\.ts?$": "ts-jest" 11 | }, 12 | 13 | moduleNameMapper: { 14 | "^@/(.*)$": "/src/$1" 15 | }, 16 | snapshotSerializers: ["jest-serializer-vue"], 17 | // testMatch: [ 18 | // "/(test/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))", 19 | // "/**/**/**/test/**" 20 | // ], 21 | transformIgnorePatterns: [ 22 | "/node_modules/", 23 | "node_modules/(?!variables/.*)" 24 | ], 25 | collectCoverage: true, 26 | collectCoverageFrom: [ 27 | "/api/**/*.js", 28 | "/utils/**/*.js", 29 | "/components/**/*.vue", 30 | "/pages/**/*.vue" 31 | ] 32 | }; 33 | // "transform": { 34 | // "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { "presets": ["next/babel"] }] 35 | // }, 36 | // "globalSetup": "./globalSetup.js", 37 | // "setupFiles": [ 38 | // "./jest.setup.js" 39 | // ], 40 | // "modulePaths": [ 41 | // "" 42 | // ], 43 | // "testEnvironment": "node", 44 | // "collectCoverageFrom": [ 45 | // "src/server/**", 46 | // "src/utils/**", 47 | // "src/pages/api/**", 48 | // "!src/server/**/**/index.ts" 49 | // ], 50 | // "moduleNameMapper": { 51 | // "^@root(.*)$": "/src$1", 52 | // "^@client(.*)$": "/src/client$1", 53 | // "^@server(.*)$": "/src/server$1" 54 | // }, 55 | // "coverageThreshold": { 56 | // "global": { 57 | // "statements": 80, 58 | // "branches": 65, 59 | // "functions": 80, 60 | // "lines": 85 61 | // } 62 | // }, 63 | // "moduleDirectories": [ 64 | // "node_modules" 65 | // ] 66 | // } 67 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 79 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/logo.png -------------------------------------------------------------------------------- /merge-to-multiple-branches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Store the current branch name 4 | CURRENT_BRANCH=$(git branch --show-current) 5 | 6 | # Branches to merge into 7 | BRANCHES=("7" "7.0" "6.12" "master") 8 | 9 | # Loop through the branches and merge the current branch into each one 10 | for BRANCH in "${BRANCHES[@]}"; do 11 | echo "Switching to branch $BRANCH" 12 | git checkout $BRANCH 13 | 14 | if [ $? -ne 0 ]; then 15 | echo "Error: Failed to switch to branch $BRANCH. Exiting." 16 | exit 1 17 | fi 18 | 19 | echo "Merging $CURRENT_BRANCH into $BRANCH" 20 | git merge $CURRENT_BRANCH 21 | 22 | if [ $? -ne 0 ]; then 23 | echo "Error: Merge failed for branch $BRANCH. Please resolve conflicts." 24 | exit 1 25 | fi 26 | 27 | echo "Pushing changes to remote for branch $BRANCH" 28 | git push origin $BRANCH 29 | 30 | if [ $? -ne 0 ]; then 31 | echo "Error: Failed to push to branch $BRANCH. Please check your connection or authentication." 32 | exit 1 33 | fi 34 | done 35 | 36 | # Switch back to the original branch 37 | echo "Switching back to $CURRENT_BRANCH" 38 | git checkout $CURRENT_BRANCH 39 | 40 | echo "Merge completed." 41 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /mqtt/getDockerDetails.ts: -------------------------------------------------------------------------------- 1 | import sanitise from "~/utils/sanitiseName"; 2 | 3 | function getDockerDetails( 4 | client, 5 | serverTitleSanitised, 6 | disabledDevices, 7 | dockerId: string, 8 | ip: string, 9 | server 10 | ) { 11 | console.log( 12 | client, 13 | serverTitleSanitised, 14 | disabledDevices, 15 | dockerId, 16 | ip, 17 | server 18 | ); 19 | 20 | if (disabledDevices.includes(`${ip}|${dockerId}`)) { 21 | return; 22 | } 23 | if (!server?.docker?.details?.containers) { 24 | return; 25 | } 26 | const docker = server.docker.details.containers[dockerId]; 27 | if (!docker) { 28 | return; 29 | } 30 | docker.name = sanitise(docker.name); 31 | 32 | if (!updated[ip]) { 33 | updated[ip] = {}; 34 | } 35 | 36 | if (!updated[ip].dockers) { 37 | updated[ip].dockers = {}; 38 | } 39 | 40 | if (updated[ip].dockers[dockerId] !== JSON.stringify(docker)) { 41 | client.publish( 42 | `${process.env.MQTTBaseTopic}/switch/${serverTitleSanitised}/${docker.name}/config`, 43 | JSON.stringify({ 44 | payload_on: "started", 45 | payload_off: "stopped", 46 | value_template: "{{ value_json.status }}", 47 | state_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}`, 48 | json_attributes_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}`, 49 | name: `${serverTitleSanitised}_docker_${docker.name}`, 50 | unique_id: `${serverTitleSanitised}_${docker.name}`, 51 | device: { 52 | identifiers: [serverTitleSanitised], 53 | name: serverTitleSanitised, 54 | manufacturer: server.serverDetails.motherboard, 55 | model: "Docker" 56 | }, 57 | command_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}/dockerState` 58 | }), 59 | { retain: false } 60 | ); 61 | client.publish( 62 | `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}`, 63 | JSON.stringify(docker), 64 | { retain: false } 65 | ); 66 | // publish restart container button 67 | client.publish( 68 | `${process.env.MQTTBaseTopic}/button/${serverTitleSanitised}/${docker.name}/config`, 69 | JSON.stringify({ 70 | payload_available: true, 71 | payload_not_available: false, 72 | value_template: "{{ value_json.status }}", 73 | state_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}`, 74 | json_attributes_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}`, 75 | name: `${serverTitleSanitised}_docker_${docker.name}_restart`, 76 | unique_id: `${serverTitleSanitised}_${docker.name}_restart`, 77 | payload_press: "restart", 78 | device: { 79 | identifiers: [serverTitleSanitised], 80 | name: serverTitleSanitised, 81 | manufacturer: server.serverDetails.motherboard, 82 | model: "Docker" 83 | }, 84 | command_topic: `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}/dockerState` 85 | }) 86 | ); 87 | client.subscribe( 88 | `${process.env.MQTTBaseTopic}/${serverTitleSanitised}/${docker.name}/dockerState` 89 | ); 90 | updated[ip].dockers[dockerId] = JSON.stringify(docker); 91 | } 92 | } 93 | 94 | export default getDockerDetails; 95 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | const colors = require("vuetify/es5/util/colors").default; 2 | const URLS = { 3 | LOGIN: "/api/login", 4 | GET_SERVERS: "/api/getServers", 5 | ARRAY_STATUS: "/api/arrayStatus", 6 | SERVER_STATUS: "/api/serverStatus", 7 | VM_STATUS: "/api/vmStatus", 8 | USB_ATTACH: "/api/usbAttach", 9 | PCI_ATTACH: "/api/pciAttach", 10 | GPU_SWAP: "/api/gpuSwap", 11 | VM_EDIT: "/api/editVM", 12 | VM_CREATE: "/api/createVM", 13 | DOCKER_STATUS: "/api/dockerStatus", 14 | MQTT_DEVICE_CHANGE: "/api/mqttDevices", 15 | DELETE_SERVER: "/api/deleteServer", 16 | PROXY_IMAGE: "/state", 17 | PROXY_IMAGE_VM: "/plugins" 18 | }; 19 | 20 | module.exports = { 21 | telemetry: false, 22 | 23 | typescript: { 24 | typeCheck: false 25 | }, 26 | 27 | buildModules: ["@nuxtjs/dotenv", "@nuxt/typescript-build"], 28 | 29 | mode: "universal", 30 | /* 31 | ** Headers of the page 32 | */ 33 | head: { 34 | titleTemplate: `%s - ${process.env.npm_package_name}`, 35 | title: process.env.npm_package_name || "", 36 | meta: [ 37 | { charset: "utf-8" }, 38 | { name: "viewport", content: "width=device-width, initial-scale=1" }, 39 | { 40 | hid: "description", 41 | name: "description", 42 | content: process.env.npm_package_description || "" 43 | } 44 | ], 45 | link: [ 46 | { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }, 47 | { 48 | rel: "stylesheet", 49 | href: 50 | "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" 51 | } 52 | ] 53 | }, 54 | /* 55 | ** Customize the progress-bar color 56 | */ 57 | loading: { color: "#fff" }, 58 | /* 59 | ** Global CSS 60 | */ 61 | css: [], 62 | /* 63 | ** Plugins to load before mounting the App 64 | */ 65 | plugins: [], 66 | /* 67 | ** Nuxt.js modules 68 | */ 69 | modules: [ 70 | "@nuxtjs/vuetify", 71 | // Doc: https://axios.nuxtjs.org/usage 72 | "@nuxtjs/axios", 73 | "@nuxtjs/pwa", 74 | "@nuxtjs/eslint-module", 75 | "~/mqtt" 76 | ], 77 | /* 78 | ** Axios module configuration 79 | ** See https://axios.nuxtjs.org/options 80 | */ 81 | axios: {}, 82 | 83 | serverMiddleware: [ 84 | // Will register file from project api directory to handle /api/* requires 85 | { path: URLS.LOGIN, handler: "~/api/login.js" }, 86 | { path: URLS.GET_SERVERS, handler: "~/api/getServers.js" }, 87 | { path: URLS.VM_STATUS, handler: "~/api/changeVMStatus.js" }, 88 | { path: URLS.DOCKER_STATUS, handler: "~/api/changeDockerStatus.js" }, 89 | { path: URLS.ARRAY_STATUS, handler: "~/api/changeArrayStatus.js" }, 90 | { path: URLS.SERVER_STATUS, handler: "~/api/changeServerStatus.js" }, 91 | { path: URLS.USB_ATTACH, handler: "~/api/usbAttach.js" }, 92 | { path: URLS.PCI_ATTACH, handler: "~/api/pciAttach.js" }, 93 | { path: URLS.GPU_SWAP, handler: "~/api/gpuSwap.js" }, 94 | { path: URLS.VM_EDIT, handler: "~/api/editVM.js" }, 95 | { path: URLS.VM_CREATE, handler: "~/api/createVM.js" }, 96 | { path: URLS.MQTT_DEVICE_CHANGE, handler: "~/api/mqttDevices.js" }, 97 | { path: URLS.DELETE_SERVER, handler: "~/api/deleteServer.js" }, 98 | { path: URLS.PROXY_IMAGE, handler: "~/api/proxyImage" }, 99 | { path: URLS.PROXY_IMAGE_VM, handler: "~/api/proxyImage" } 100 | ], 101 | /* 102 | ** vuetify module configuration 103 | ** https://github.com/nuxt-community/vuetify-module 104 | */ 105 | vuetify: { 106 | theme: { 107 | primary: colors.blue.darken2, 108 | accent: colors.grey.darken3, 109 | secondary: colors.amber.darken3, 110 | info: colors.teal.lighten1, 111 | warning: colors.amber.base, 112 | error: colors.deepOrange.accent4, 113 | success: colors.green.accent3 114 | } 115 | }, 116 | /* 117 | ** Build configuration 118 | */ 119 | build: { 120 | /* 121 | ** You can extend webpack config here 122 | */ 123 | extend(config, ctx) {} 124 | } 125 | }; 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unraidapi", 3 | "version": "0.5.0", 4 | "description": "A new UI and API for controlling multiple unraid instances", 5 | "author": "Will", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint --ext .js,.vue --ignore-path ../.gitignore .", 9 | "precommit": "npm run lint", 10 | "test": "ts-node test-runner.ts", 11 | "dev": "nodemon -e ts,js --exec \"node -r dotenv/config\" --max-old-space-size=4096 server/index.js --watch", 12 | "build": "nuxt build", 13 | "start": "cross-env NUXT_HOST=0.0.0.0 NODE_ENV=production node server/index.js", 14 | "generate": "nuxt generate" 15 | }, 16 | "dependencies": { 17 | "@babel/core": "^7.12.9", 18 | "@nuxt/types": "^2.17.1", 19 | "@nuxt/typescript-build": "^3.0.1", 20 | "@nuxtjs/axios": "^5.12.3", 21 | "@nuxtjs/dotenv": "^1.4.1", 22 | "@nuxtjs/eslint-module": "^0.0.1", 23 | "@nuxtjs/pwa": "^2.6.0", 24 | "@nuxtjs/vuetify": "0.5.5", 25 | "@types/jsdom": "^21.1.1", 26 | "axios": "^0.21.1", 27 | "cheerio": "^1.0.0-rc.12", 28 | "cross-env": "^5.2.1", 29 | "express": "^4.16.4", 30 | "form-data": "^2.5.1", 31 | "fs": "0.0.1-security", 32 | "html-table-to-json": "^0.4.1", 33 | "http": "0.0.0", 34 | "js-base64": "^2.6.4", 35 | "mqtt": "^3.0.0", 36 | "node-fetch": "^2.6.1", 37 | "nuxt": "^2.14.10", 38 | "uniqid": "^5.2.0", 39 | "vuetify-loader": "^1.6.0", 40 | "winston": "^3.14.2" 41 | }, 42 | "devDependencies": { 43 | "@babel/preset-typescript": "^7.22.5", 44 | "@nuxtjs/eslint-config": "^0.0.1", 45 | "@types/jest": "^29.5.3", 46 | "@types/node-fetch": "^2.6.6", 47 | "@types/uniqid": "^5.3.2", 48 | "@typescript-eslint/eslint-plugin": "^5.0.0", 49 | "@typescript-eslint/parser": "^5.0.0", 50 | "@vue/test-utils": "^1.1.1", 51 | "babel-core": "7.0.0-bridge.0", 52 | "babel-eslint": "^10.1.0", 53 | "babel-jest": "^29.6.1", 54 | "babel-plugin-dynamic-import-node": "^2.3.3", 55 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 56 | "babel-plugin-transform-runtime": "^6.23.0", 57 | "babel-preset-latest": "^6.24.1", 58 | "chai": "^4.2.0", 59 | "eslint": "^7.1.0", 60 | "eslint-config-prettier": "^4.1.0", 61 | "eslint-config-standard": "^16.0.2", 62 | "eslint-plugin-import": "^2.22.1", 63 | "eslint-plugin-jest": "^24.1.3", 64 | "eslint-plugin-node": "^11.1.0", 65 | "eslint-plugin-nuxt": "^2.0.0", 66 | "eslint-plugin-prettier": "^3.2.0", 67 | "eslint-plugin-promise": "^4.2.1", 68 | "eslint-plugin-standard": "^5.0.0", 69 | "eslint-plugin-vue": "^6.2.2", 70 | "jest": "^29.6.1", 71 | "jest-serializer-vue": "^2.0.2", 72 | "jest-transform-stub": "^2.0.0", 73 | "nodemon": "^1.19.4", 74 | "prettier": "^1.19.1", 75 | "stylus": "^0.54.8", 76 | "stylus-loader": "^3.0.2", 77 | "ts-jest": "^29.1.1", 78 | "typescript": "^5.1.6", 79 | "vue-jest": "^3.0.7" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 138 | -------------------------------------------------------------------------------- /pages/mqtt.vue: -------------------------------------------------------------------------------- 1 | 116 | 117 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /repository.yaml: -------------------------------------------------------------------------------- 1 | # https://developers.home-assistant.io/docs/add-ons/repository#repository-configuration 2 | name: Unraid-API-RE 3 | url: 'https://github.com/BoKKeR/UnraidAPI-RE/' 4 | maintainer: Norbert Takacs -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const consola = require("consola"); 3 | const { Nuxt, Builder } = require("nuxt"); 4 | const app = express(); 5 | 6 | // Import and Set Nuxt.js options 7 | const config = require("../nuxt.config.js"); 8 | config.dev = !(process.env.NODE_ENV === "production"); 9 | 10 | async function start() { 11 | // Init Nuxt.js 12 | const nuxt = new Nuxt(config); 13 | 14 | const { 15 | host = process.env.HOST || "0.0.0.0", 16 | port = process.env.PORT || 3000 17 | } = nuxt.options.server; 18 | 19 | // Build only in dev mode 20 | if (config.dev) { 21 | const builder = new Builder(nuxt); 22 | await builder.build(); 23 | } else { 24 | await nuxt.ready(); 25 | } 26 | 27 | // Give nuxt middleware to express 28 | app.use(nuxt.render); 29 | 30 | // Listen the server 31 | app.listen(port, host); 32 | consola.ready({ 33 | message: `Server listening on http://${host}:${port}`, 34 | badge: true 35 | }); 36 | } 37 | start(); 38 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/static/favicon.ico -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/static/icon.png -------------------------------------------------------------------------------- /static/iconx64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/static/iconx64.png -------------------------------------------------------------------------------- /static/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('/_nuxt/workbox.4c4f5ca6.js') 2 | 3 | workbox.precaching.precacheAndRoute([ 4 | { 5 | "url": "/_nuxt/7715ae1.js", 6 | "revision": "14c66ca5b324da27f3d4008580faf1d7" 7 | }, 8 | { 9 | "url": "/_nuxt/852e878.js", 10 | "revision": "15e1028a227aca0e7932bfaba648838f" 11 | }, 12 | { 13 | "url": "/_nuxt/8c82a02.js", 14 | "revision": "744d03381449003244d6a416a225c8f4" 15 | }, 16 | { 17 | "url": "/_nuxt/d5af821.js", 18 | "revision": "c31a8cb5a42fa293258d0e43efed0226" 19 | }, 20 | { 21 | "url": "/_nuxt/ec9b91f.js", 22 | "revision": "b39c57b474abc1089b5a2977cd60295e" 23 | }, 24 | { 25 | "url": "/_nuxt/f14dc79.js", 26 | "revision": "2dc6f967c37e522b99470006487854ae" 27 | }, 28 | { 29 | "url": "/_nuxt/ff39b8b.js", 30 | "revision": "af34826574c46daff7313be3bc490420" 31 | } 32 | ], { 33 | "cacheId": "unraidapi", 34 | "directoryIndex": "/", 35 | "cleanUrls": false 36 | }) 37 | 38 | workbox.clientsClaim() 39 | workbox.skipWaiting() 40 | 41 | workbox.routing.registerRoute(new RegExp('/_nuxt/.*'), workbox.strategies.cacheFirst({}), 'GET') 42 | 43 | workbox.routing.registerRoute(new RegExp('/.*'), workbox.strategies.networkFirst({}), 'GET') 44 | -------------------------------------------------------------------------------- /static/unraid.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/static/unraid.jpeg -------------------------------------------------------------------------------- /static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoKKeR/UnraidAPI-RE/f740d575322221b7c58f464a5016dce897ca93f4/static/v.png -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /test-runner.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as process from "process"; 3 | import * as child_process from "child_process"; 4 | import { setTimeout } from "timers/promises"; 5 | 6 | const directoryPath = "./unraid-versions"; 7 | 8 | const testFolderParam = process.argv[2]; 9 | 10 | const main = async () => { 11 | // Read the directory 12 | const files = fs.readdirSync(directoryPath); 13 | 14 | if (testFolderParam) { 15 | await testVersion(testFolderParam); 16 | } else { 17 | // Assuming each folder name is a valid environment variable 18 | for (const folder of files) { 19 | if (folder !== ".DS_Store") { 20 | // Set environment variable based on folder name 21 | await testVersion(folder); 22 | } 23 | } 24 | } 25 | }; 26 | 27 | const testVersion = async (folder: string) => { 28 | // Set environment variable based on folder name 29 | process.env.UNRAID_VERSION = folder; 30 | console.log(`Set environment variable ${folder} to ${folder}`); 31 | 32 | // Execute Jest for each folder 33 | const jestProcess = child_process.spawn( 34 | "WRITE_HTML_OUTPUT=false && ./node_modules/.bin/jest --coverage=false --verbose=false", 35 | [], 36 | { 37 | stdio: "inherit", 38 | shell: true, 39 | env: { ...process.env } // Pass the current environment along with the new variable 40 | } 41 | ); 42 | await setTimeout(5000); 43 | 44 | jestProcess.on("exit", (code, signal) => { 45 | console.log(`Jest process for ${folder} exited with code ${code}`); 46 | }); 47 | }; 48 | 49 | main(); 50 | -------------------------------------------------------------------------------- /test/unit/utils/Unraid-not-used-test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addPCICheck, 3 | extractReverseValue, 4 | flipPCICheck, 5 | getCPUPart, 6 | getDiskPart, 7 | getNetworkPart, 8 | getPCIDetails, 9 | getPCIPart, 10 | getSharePart, 11 | getStaticPart, 12 | getUSBPart, 13 | removePCICheck 14 | } from "../../../utils/Unraid"; 15 | import { extractValue } from "../../../utils/extractValue"; 16 | 17 | describe("PCI Check Changes", () => { 18 | let examplePCIInput = { 19 | pcis: [ 20 | { 21 | id: "00:00.1", 22 | checked: false 23 | }, 24 | { 25 | id: "00:00.2" 26 | }, 27 | { 28 | id: "00:01.1", 29 | checked: false 30 | }, 31 | { 32 | id: "00:02.1", 33 | checked: false 34 | }, 35 | { 36 | id: "01:00.1", 37 | checked: false 38 | } 39 | ] 40 | }; 41 | 42 | let expectedPCIFlipOutput = { 43 | pcis: [ 44 | { 45 | id: "00:00.1", 46 | checked: true 47 | }, 48 | { 49 | id: "00:00.2", 50 | checked: true 51 | }, 52 | { 53 | id: "00:01.1", 54 | checked: false 55 | }, 56 | { 57 | id: "00:02.1", 58 | checked: false 59 | }, 60 | { 61 | id: "01:00.1", 62 | checked: false 63 | } 64 | ] 65 | }; 66 | 67 | let expectedPCIAddOutput = { 68 | pcis: [ 69 | { 70 | id: "00:00.1", 71 | checked: true 72 | }, 73 | { 74 | id: "00:00.2", 75 | checked: true 76 | }, 77 | { 78 | id: "00:01.1", 79 | checked: false 80 | }, 81 | { 82 | id: "00:02.1", 83 | checked: false 84 | }, 85 | { 86 | id: "01:00.1", 87 | checked: false 88 | } 89 | ] 90 | }; 91 | 92 | let expectedPCIRemoveOutput = { 93 | pcis: [ 94 | { 95 | id: "00:00.1", 96 | checked: false 97 | }, 98 | { 99 | id: "00:00.2", 100 | checked: false 101 | }, 102 | { 103 | id: "00:01.1", 104 | checked: false 105 | }, 106 | { 107 | id: "00:02.1", 108 | checked: false 109 | }, 110 | { 111 | id: "01:00.1", 112 | checked: false 113 | } 114 | ] 115 | }; 116 | 117 | let examplePCIInputOddState = { 118 | pcis: [ 119 | { 120 | id: "00:00.1", 121 | checked: false 122 | }, 123 | { 124 | id: "00:00.2", 125 | checked: true 126 | }, 127 | { 128 | id: "00:01.1", 129 | checked: false 130 | }, 131 | { 132 | id: "00:02.1", 133 | checked: false 134 | }, 135 | { 136 | id: "01:00.1", 137 | checked: false 138 | } 139 | ] 140 | }; 141 | 142 | let expectedPCIFlipOutputOddState = { 143 | pcis: [ 144 | { 145 | id: "00:00.1", 146 | checked: true 147 | }, 148 | { 149 | id: "00:00.2", 150 | checked: true 151 | }, 152 | { 153 | id: "00:01.1", 154 | checked: false 155 | }, 156 | { 157 | id: "00:02.1", 158 | checked: false 159 | }, 160 | { 161 | id: "01:00.1", 162 | checked: false 163 | } 164 | ] 165 | }; 166 | 167 | test("flips PCI check of all related devices", () => { 168 | flipPCICheck(examplePCIInput, "00:00.1"); 169 | expect(examplePCIInput).toEqual(expectedPCIFlipOutput); 170 | }); 171 | 172 | test("flips PCI check of all related devices, handle odd state", () => { 173 | flipPCICheck(examplePCIInputOddState, "00:00.1"); 174 | expect(examplePCIInputOddState).toEqual(expectedPCIFlipOutputOddState); 175 | }); 176 | 177 | test("adds PCI check of all related devices", () => { 178 | addPCICheck(examplePCIInput, "00:00.1"); 179 | expect(examplePCIInput).toEqual(expectedPCIAddOutput); 180 | }); 181 | 182 | test("removes PCI check of all related devices", () => { 183 | removePCICheck(examplePCIInput, "00:00.1"); 184 | expect(examplePCIInput).toEqual(expectedPCIRemoveOutput); 185 | }); 186 | }); 187 | 188 | describe("VM Change Form", () => { 189 | const staticInput = { 190 | template_os: "testValue", 191 | template_name: "testValue", 192 | template_icon: "testValue", 193 | domain_persistent: "testValue", 194 | domain_type: "testValue", 195 | domain_clock: "testValue", 196 | domain_oldname: "testValue", 197 | domain_name: "testValue", 198 | domain_arch: "testValue", 199 | domain_desc: "testValue", 200 | domain_cpumode: "testValue", 201 | domain_ovmf: "testValue", 202 | domain_mem: "testValue", 203 | domain_maxmem: "testValue", 204 | domain_machine: "testValue", 205 | domain_hyperv: "testValue", 206 | domain_usbmode: "testValue", 207 | media_cdrom: "testValue", 208 | media_cdrombus: "testValue", 209 | media_drivers: "testValue", 210 | media_driversbus: "testValue" 211 | }; 212 | 213 | const cpuInput = { 214 | vcpus: [0, 2, 4] 215 | }; 216 | 217 | const diskInput = { 218 | disks: [ 219 | { 220 | image: "testImage", 221 | select: "testSelect", 222 | size: "testSize", 223 | driver: "testDriver", 224 | bus: "testBus" 225 | } 226 | ] 227 | }; 228 | 229 | const shareInput = { 230 | shares: [ 231 | { 232 | source: "testSource", 233 | target: "testTarget" 234 | } 235 | ] 236 | }; 237 | 238 | const pciInput = { 239 | pcis: [ 240 | { 241 | id: "testID", 242 | checked: true 243 | }, 244 | { 245 | id: "testID2", 246 | checked: true, 247 | gpu: true 248 | }, 249 | { 250 | id: "testID3", 251 | checked: true, 252 | audio: true 253 | }, 254 | { 255 | id: "testID4" 256 | }, 257 | { 258 | id: "testID5", 259 | checked: false, 260 | gpu: true 261 | }, 262 | { 263 | id: "testID6", 264 | audio: true 265 | }, 266 | { 267 | id: "vnc", 268 | checked: true, 269 | gpu: true 270 | } 271 | ] 272 | }; 273 | 274 | const usbInput = { 275 | usbs: [ 276 | { 277 | id: "testID", 278 | checked: true 279 | }, 280 | { 281 | id: "testID2" 282 | } 283 | ] 284 | }; 285 | 286 | const networkInput = { 287 | nics: [ 288 | { 289 | mac: "testMac", 290 | network: "testNetwork" 291 | }, 292 | { 293 | mac: "testMac2", 294 | network: "testNetwork2" 295 | } 296 | ] 297 | }; 298 | 299 | const expectedStaticPart = 300 | "template%5Bos%5D=testValuetemplate%5Bname%5D=testValuetemplate%5Bicon%5D=testValue&domain%5Bpersistent%5D=testValue&domain%5Btype%5D=testValue&domain%5Bautostart%5D=1&domain%5Buuid%5D=testID&domain%5Bclock%5D=testValue&domain%5Boldname%5D=testValue&domain%5Bname%5D=testValue&domain%5Barch%5D=testValue&domain%5Bdesc%5D=testValue&domain%5Bcpumode%5D=testValue&domain%5Bovmf%5D=testValue&domain%5Bmem%5D=testValue&domain%5Bmaxmem%5D=testValue&domain%5Bmachine%5D=testValue&domain%5Bhyperv%5D=testValue&domain%5Busbmode%5D=testValue&media%5Bcdrom%5D=testValue&media%5Bcdrombus%5D=testValue&media%5Bdrivers%5D=testValue&media%5Bdriversbus%5D=testValue&updatevm=1&domain%5Bpassword%5D="; 301 | const expectedStaticPartCreate = 302 | "template%5Bos%5D=testValuetemplate%5Bname%5D=testValuetemplate%5Bicon%5D=testValue&domain%5Bpersistent%5D=testValue&domain%5Btype%5D=testValue&domain%5Bautostart%5D=1&domain%5Buuid%5D=testID&domain%5Bclock%5D=testValue&domain%5Boldname%5D=testValue&domain%5Bname%5D=testValue&domain%5Barch%5D=testValue&domain%5Bdesc%5D=testValue&domain%5Bcpumode%5D=testValue&domain%5Bovmf%5D=testValue&domain%5Bmem%5D=testValue&domain%5Bmaxmem%5D=testValue&domain%5Bmachine%5D=testValue&domain%5Bhyperv%5D=testValue&domain%5Busbmode%5D=testValue&media%5Bcdrom%5D=testValue&media%5Bcdrombus%5D=testValue&media%5Bdrivers%5D=testValue&media%5Bdriversbus%5D=testValue&createvm=1&domain%5Bpassword%5D="; 303 | const expectedCPUPart = 304 | "&domain%5Bvcpu%5D%5B%5D=0&domain%5Bvcpu%5D%5B%5D=2&domain%5Bvcpu%5D%5B%5D=4"; 305 | const expectedDiskPart = 306 | "&disk%5B0%5D%5Bimage%5D=testImage&disk%5B0%5D%5Bselect%5D=testSelect&disk%5B0%5D%5Bsize%5D=testSize&disk%5B0%5D%5Bdriver%5D=testDriver&disk%5B0%5D%5Bbus%5D=testBus"; 307 | const expectedSharePart = 308 | "&shares%5B0%5D%5Bsource%5D=testSource&shares%5B0%5D%5Btarget%5D=testTarget"; 309 | const expectedPCIPart = 310 | "&pci%5B%5D=testID&gpu%5B0%5D%5Bid%5D=testID2&gpu%5B0%5D%5Bmodel%5D=qxl&gpu%5B0%5D%5Bkeymap%5D=&gpu%5B0%5D%5Bbios%5D=&audio%5B0%5D%5Bid%5D=testID3&pci%5B%5D=testID4%23remove&pci%5B%5D=testID5%23remove&pci%5B%5D=testID6%23remove"; 311 | const expectedUSBPart = "&usb%5B%5D=testID&usb%5B%5D=testID2%23remove"; 312 | const expectedNetworkPart = 313 | "&nic%5B0%5D%5Bmac%5D=testMac&nic%5B0%5D%5Bnetwork%5D=testNetwork&nic%5B1%5D%5Bmac%5D=testMac2&nic%5B1%5D%5Bnetwork%5D=testNetwork2"; 314 | 315 | test("Get Static Part (Edit)", () => { 316 | expect(getStaticPart(staticInput, "testID", false)).toEqual( 317 | expectedStaticPart 318 | ); 319 | }); 320 | 321 | test("Get Static Part (Create)", () => { 322 | expect(getStaticPart(staticInput, "testID", true)).toEqual( 323 | expectedStaticPartCreate 324 | ); 325 | }); 326 | 327 | test("Get CPU Part", () => { 328 | expect(getCPUPart(cpuInput, "")).toEqual(expectedCPUPart); 329 | }); 330 | 331 | test("Get Disk Part", () => { 332 | expect(getDiskPart(diskInput, "")).toEqual(expectedDiskPart); 333 | }); 334 | 335 | test("Get Share Part", () => { 336 | expect(getSharePart(shareInput, "")).toEqual(expectedSharePart); 337 | }); 338 | 339 | test("Get PCI Part", () => { 340 | expect(getPCIPart(pciInput, "")).toEqual(expectedPCIPart); 341 | }); 342 | 343 | test("Get USB Part", () => { 344 | expect(getUSBPart(usbInput, "")).toEqual(expectedUSBPart); 345 | }); 346 | 347 | test("Get Network Part", () => { 348 | expect(getNetworkPart(networkInput, "")).toEqual(expectedNetworkPart); 349 | }); 350 | }); 351 | 352 | describe("Detail Extraction", () => { 353 | const expectedUnraidDetails = { 354 | "1": { 355 | pciDetails: [ 356 | { id: "1", name: "test" }, 357 | { id: "21", name: "test2" } 358 | ], 359 | vm: { 360 | details: { 361 | testVM: { 362 | edit: { 363 | pcis: [ 364 | { id: "1", name: "test" }, 365 | { id: "21", name: "test2" } 366 | ] 367 | } 368 | } 369 | } 370 | } 371 | } 372 | }; 373 | 374 | test("Extract Value from HTML", () => { 375 | const inputToExtractFrom = 376 | 'aloadOfDataWithRandomtest'; 377 | expect(extractValue(inputToExtractFrom, 'with="', '"')).toEqual( 378 | "attributes" 379 | ); 380 | expect(extractValue(inputToExtractFrom, '">', "<")).toEqual("test"); 381 | }); 382 | 383 | test("Extract Value from HTML by reversing the string", () => { 384 | const inputToExtractFrom = 385 | 'aloadOfDataWithRandomtesttest'; 386 | expect( 387 | extractReverseValue( 388 | extractValue(inputToExtractFrom, ""), 389 | '"', 390 | 'with="' 391 | ) 392 | ).toEqual("values"); 393 | }); 394 | 395 | test("Get PCI Details", () => { 396 | let inputDetails = { 397 | "1": { 398 | vm: { 399 | details: { 400 | testVM: { 401 | edit: { 402 | pcis: [ 403 | { name: "test", id: "1" }, 404 | { name: "test2", id: "21" } 405 | ] 406 | } 407 | } 408 | } 409 | } 410 | } 411 | }; 412 | getPCIDetails(inputDetails, true); 413 | expect(inputDetails).toEqual(expectedUnraidDetails); 414 | }); 415 | }); 416 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "lib": [ 7 | "ESNext", 8 | "ESNext.AsyncIterable", 9 | "DOM" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "noImplicitAny": false, 14 | "sourceMap": true, 15 | "strict": true, 16 | "noEmit": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "~/*": [ 20 | "./*" 21 | ], 22 | "@/*": [ 23 | "./*" 24 | ] 25 | }, 26 | "types": [ 27 | "@nuxt/types", 28 | "@nuxt/typescript-build", 29 | "@types/node", 30 | "@types/jest" 31 | ] 32 | }, 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } -------------------------------------------------------------------------------- /types/json-server.ts: -------------------------------------------------------------------------------- 1 | export type Disk = { 2 | select: string; 3 | image: string; 4 | driver: string; 5 | bus: string; 6 | size: string; 7 | }; 8 | 9 | export type ShareData = { 10 | source: string; 11 | target: string; 12 | }; 13 | 14 | export type UsbData = { 15 | id?: string; 16 | attached?: boolean; 17 | name?: string; 18 | connected?: boolean; 19 | checked?: boolean; 20 | }; 21 | 22 | export type PCIData = { 23 | gpu?: boolean; 24 | id: string; 25 | name: string; 26 | checked: boolean; 27 | position?: number; 28 | bios?: string; 29 | sound?: boolean; 30 | }; 31 | 32 | export type GPUData = { 33 | gpu: boolean; 34 | id: string; 35 | name: string; 36 | checked: boolean; 37 | position: number; 38 | model?: string; 39 | keymap?: string; 40 | bios: string; 41 | }; 42 | 43 | export type VMData = { 44 | name: string; 45 | id: string; 46 | status: string; 47 | icon: string; 48 | coreCount: string; 49 | ramAllocation: string; 50 | hddAllocation: HDDAllocationInfo; 51 | primaryGPU: string; 52 | }; 53 | 54 | export type HDDAllocationInfo = { 55 | all: any; 56 | total: string; 57 | }; 58 | 59 | export type HDD = { 60 | path: string; 61 | interface: string; 62 | allocated: string; 63 | used: string; 64 | }; 65 | 66 | export type DockerDetail = { 67 | imageUrl: string; 68 | name: string; 69 | status: string; 70 | containerId: string; 71 | tag: string; 72 | uptoDate: string; 73 | imageId: string; 74 | created: string; 75 | details: { 76 | containers: { 77 | [key: string]: ContainerDetail; 78 | }; 79 | images: { 80 | [key: string]: ImageDetail; 81 | }; 82 | }; 83 | }; 84 | 85 | export type ContainerStatus = "started" | "stopped"; 86 | 87 | export type ContainerDetail = { 88 | imageUrl: string; 89 | name: string; 90 | status: ContainerStatus; 91 | containerId: string; 92 | tag: string; 93 | }; 94 | 95 | /* imageUrl: '/webGui/images/disk.png', 96 | imageId: '9c5683ec06a8', 97 | created: 'Created 2 months ago' */ 98 | export type ImageDetail = { 99 | imageUrl: string; 100 | imageId: string; 101 | created: string; 102 | }; 103 | 104 | export type SoundData = { 105 | sound: boolean; 106 | name: string; 107 | id: string; 108 | checked: boolean; 109 | }; 110 | 111 | export type NicData = { 112 | mac: string; 113 | network: string; 114 | }; 115 | -------------------------------------------------------------------------------- /types/server.ts: -------------------------------------------------------------------------------- 1 | import { Disk, DockerDetail, PCIData, UsbData } from "./json-server"; 2 | 3 | export type DockerAction = "domain-start" | "domain-restart" | "domain-stop"; 4 | export type VMAction = 5 | | "domain-resume" 6 | | "domain-pause" 7 | | "domain-destroy" 8 | | "domain-pmsuspend"; 9 | export interface RootServerJSONConfig { 10 | [key: string]: ServerJSONConfig; 11 | } 12 | 13 | export interface ServerJSONConfig { 14 | docker?: DockerDetail; 15 | serverDetails: ServerDetails; 16 | vm?: Vm; 17 | pciDetails?: PCIData[]; 18 | status: string; 19 | usbDetails?: any[]; 20 | } 21 | 22 | export interface DockerObject { 23 | details: Details; 24 | } 25 | 26 | export interface Details { 27 | images: DockerImages; 28 | containers: Containers; 29 | } 30 | 31 | export interface DockerImages { 32 | [key: string]: DockerImage; 33 | } 34 | 35 | export interface DockerImage { 36 | imageUrl: string; 37 | imageId: string; 38 | created: string; 39 | } 40 | 41 | export type Containers = { 42 | [key: string]: DockerDetail; 43 | }; 44 | 45 | export interface ServerDetails { 46 | arrayStatus: string; 47 | arrayProtection: string; 48 | moverRunning: boolean; 49 | parityCheckRunning: boolean; 50 | title: string; 51 | cpu: string; 52 | memory: string; 53 | motherboard: string; 54 | diskSpace: string; 55 | cacheSpace: string; 56 | version: string; 57 | arrayUsedSpace: string; 58 | arrayTotalSpace: string; 59 | arrayFreeSpace: string; 60 | cacheUsedSpace: string; 61 | cacheTotalSpace: string; 62 | cacheFreeSpace: string; 63 | on: boolean; 64 | 65 | vmEnabled?: boolean; 66 | dockerEnabled?: boolean; 67 | } 68 | 69 | export interface Vm { 70 | extras: string; 71 | details: VmDetails; 72 | } 73 | 74 | export interface VmDetails { 75 | [key: string]: VmDetail; // probably same as VMData 76 | } 77 | 78 | export interface VmDetail { 79 | name: string; 80 | id: string; 81 | status: string; 82 | icon: string; 83 | coreCount?: string; 84 | mac?: string; 85 | ramAllocation: string; 86 | ram?: string; 87 | hddAllocation: HddAllocation; 88 | primaryGPU?: string; 89 | xml: string; 90 | description?: string; 91 | edit: Edit; 92 | } 93 | 94 | export interface HddAllocation { 95 | all: All[]; 96 | total: string; 97 | } 98 | 99 | export interface All { 100 | path: string; 101 | interface: string; 102 | } 103 | 104 | export interface Edit { 105 | description: string; 106 | template_os: string; 107 | domain_type: string; 108 | template_name: string; 109 | template_icon: string; 110 | domain_persistent: string; 111 | domain_clock: string; 112 | domain_arch: string; 113 | domain_oldname: string; 114 | domain_name: string; 115 | domain_desc: string; 116 | domain_cpumode: string; 117 | domain_mem: string; 118 | domain_maxmem: string; 119 | domain_machine: string; 120 | domain_hyperv: string; 121 | domain_usbmode: string; 122 | domain_ovmf: string; 123 | media_cdrom: string; 124 | media_cdrombus: string; 125 | media_drivers: string; 126 | media_driversbus: string; 127 | gpu_bios: string; 128 | nic_0_mac: string; 129 | vcpus: string[]; 130 | disks: Disk[]; 131 | shares: any[]; 132 | usbs: UsbData[]; 133 | pcis: Pci[]; 134 | nics: Nic[]; 135 | } 136 | 137 | export interface Pci { 138 | gpu?: boolean; 139 | id: string; 140 | name: string; 141 | checked: boolean; 142 | position?: number; 143 | bios?: string; 144 | sound?: boolean; 145 | } 146 | 147 | export interface Nic { 148 | mac: string; 149 | network: string; 150 | } 151 | -------------------------------------------------------------------------------- /unraid-versions/6.12.10/virtualMachines.html: -------------------------------------------------------------------------------- 1 | Linux
stopped
12048M1 / 10GVNC:auto
Disk devicesSerialBusCapacityAllocationBoot Order
/mnt/user/domains/Linux/vdisk1.imgvdisk1VirtIO
10G
4K1
InterfacesTypeIP AddressPrefix
Guest not running
var kvm=[];kvm.push({id:'c5825689-86ae-3de8-cb03-a875914c8803',state:'shutoff'}); -------------------------------------------------------------------------------- /unraid-versions/6.12.10/vmObject.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Linux", 3 | "id": "c5825689-86ae-3de8-cb03-a875914c8803", 4 | "status": "stopped", 5 | "icon": "/plugins/dynamix.vm.manager/templates/images/linux.png", 6 | "coreCount": "1", 7 | "ramAllocation": "2048M", 8 | "hddAllocation": { 9 | "all": [{ "path": " ", "interface": "Serial" }], 10 | "total": "1 / 10G" 11 | }, 12 | "primaryGPU": "VNC:auto", 13 | "xml": "\n\n Linux\n c5825689-86ae-3de8-cb03-a875914c8803\n \n \n \n 2097152\n 2097152\n \n \n \n 1\n \n \n \n \n hvm\n /usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd\n /etc/libvirt/qemu/nvram/c5825689-86ae-3de8-cb03-a875914c8803_VARS-pure-efi.fd\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n destroy\n restart\n restart\n \n /usr/local/sbin/qemu\n \n \n \n \n vdisk1\n \n
\n \n \n
\n \n \n \n
\n \n \n \n
\n \n \n \n
\n \n \n \n \n \n
\n \n \n \n \n
\n \n \n \n \n
\n \n \n \n \n
\n \n \n \n \n
\n \n \n
\n \n \n
\n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n
\n \n \n \n \n \n \n