├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
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 |
2 |
3 |
4 |
5 | Edit
6 |
7 |
8 |
9 |
10 |
11 | {{ vm.name }}
12 |
13 |
14 |
15 | This will contain edit options.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Confirm
24 |
25 |
26 |
27 |
28 |
29 |
30 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/components/GpuSwap.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GPU Swap
6 |
7 |
8 |
9 |
10 |
11 | Swap Primary GPUs
12 |
13 |
14 |
15 |
24 |
25 |
33 |
34 |
41 | add
42 |
43 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Confirm
60 |
61 |
62 |
63 |
64 |
65 |
66 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/components/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
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 |
2 |
3 |
4 |
10 |
11 | Setup Unraid Server
12 |
13 |
14 |
15 |
22 |
29 |
30 |
37 |
45 |
46 | {{ loginMessage }}
47 |
48 |
49 |
50 |
51 | Login
52 |
53 |
54 |
55 |
56 |
57 |
58 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/components/UsbDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 | {{ detail.name }}
12 |
13 |
21 |
22 | eject
23 |
24 |
25 |
26 |
27 |
35 |
36 | cached
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 | {{ detail.name }}
49 |
50 |
51 |
52 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
70 | Confirm
71 |
72 |
73 |
74 |
75 |
76 |
77 |
199 |
200 |
--------------------------------------------------------------------------------
/components/VuetifyLogo.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
22 |
--------------------------------------------------------------------------------
/components/documentation/EditDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | {{title}}
7 |
8 |
9 |
15 |
16 |
17 |
18 |
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 |
2 |
3 |
10 |
11 |
18 |
19 | {{ item.icon }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | © 2024 Forked by BoKKeR. ⭐ the
40 | repo to keep me
41 | employable.
43 |
44 |
45 |
46 |
47 |
79 |
--------------------------------------------------------------------------------
/layouts/error.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ pageNotFound }}
5 |
6 |
7 | {{ otherError }}
8 |
9 |
10 | Home page
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Please Enter Your Password For: {{ checkIp }}
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
39 |
40 |
41 |
42 | *indicates required field
43 |
44 |
45 |
46 | Cancel
49 | Confirm
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
138 |
--------------------------------------------------------------------------------
/pages/mqtt.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Servers
6 |
7 |
8 |
9 | {{ ip }}
10 |
11 |
12 | Delete
13 |
18 |
19 |
20 |
21 |
22 |
23 | VMs
24 |
25 |
26 |
31 |
32 |
33 |
34 |
38 |
39 | {{ vm.name }}
40 |
41 |
42 |
47 |
48 |
49 |
50 |
51 |
52 | USBs
53 |
54 |
55 |
60 |
61 |
62 | {{ usb.name }}
63 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Docker
79 |
80 |
81 |
86 |
87 |
88 |
89 |
93 |
94 | {{ docker.name }}
95 |
96 |
97 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | Submit
111 |
112 |
113 |
114 |
115 |
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 | | 1 | 2048M | 1 / 10G | VNC:auto | |
Disk devices | Serial | Bus | Capacity | Allocation | Boot Order |
---|
/mnt/user/domains/Linux/vdisk1.img | vdisk1 | VirtIO | | 4K | 1 | Interfaces | | | Type | IP Address | Prefix |
---|
Guest not running | | | | |
|