fmt: Code style and linter

This commit is contained in:
jamesread 2026-07-18 22:46:05 +01:00
parent e2bf82ff9c
commit 25b80e1460
66 changed files with 4062 additions and 2775 deletions

View File

@ -18,6 +18,7 @@ Please put a X in the boxes as evidence of reading through the checklist.
- [ ] I followed the "1 logical change" rule. - [ ] I followed the "1 logical change" rule.
- [ ] I have forked the project, and raised this PR on a feature branch. - [ ] I have forked the project, and raised this PR on a feature branch.
- [ ] I ran the `pre-commit` hooks, and my commit message was validated. - [ ] I ran the `pre-commit` hooks, and my commit message was validated.
- [ ] I installed the pre-push hook (`pre-commit install --hook-type pre-push`) and integration tests pass on push when relevant files changed.
- [ ] `make -wC service compile` runs without any issues. - [ ] `make -wC service compile` runs without any issues.
- [ ] `make -wC service codestyle` runs without any issues. - [ ] `make -wC service codestyle` runs without any issues.
- [ ] `make -wC service unittests` runs without any issues. - [ ] `make -wC service unittests` runs without any issues.

View File

@ -38,32 +38,33 @@ repos:
entry: make service-codestyle entry: make service-codestyle
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true files: ^(service/|proto/|lang/)
- id: frontend-codestyle - id: frontend-codestyle
name: frontend-codestyle name: frontend-codestyle
entry: make frontend-codestyle entry: make frontend-codestyle
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true files: ^frontend/
- id: service-unittests - id: service-unittests
name: service-unittests name: service-unittests
entry: make service-unittests entry: make service-unittests
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true files: ^(service/|proto/|lang/)
- id: service-build - id: service-build
name: service-build name: service-build
entry: make service entry: make service
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true files: ^(service/|proto/|lang/)
- id: it - id: it
name: integration-tests name: integration-tests
entry: make it entry: make it
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true stages: [pre-push]
files: ^(service/|frontend/|integration-tests/|proto/|Makefile)

View File

@ -43,6 +43,7 @@ choco install git go protoc make python nodejs-lts -y && pip install pre-commit
git clone https://github.com/OliveTin/OliveTin.git git clone https://github.com/OliveTin/OliveTin.git
cd OliveTin cd OliveTin
pre-commit install pre-commit install
pre-commit install --hook-type pre-push
# Step3: compile binary for current dev env (OS, ARCH) # Step3: compile binary for current dev env (OS, ARCH)
# `make proto` will also run `make go-tools`, which installs "buf". This binary # `make proto` will also run `make go-tools`, which installs "buf". This binary

View File

@ -4,12 +4,22 @@
"es2021": true "es2021": true
}, },
"extends": [ "extends": [
"plugin:vue/recommended",
"standard" "standard"
], ],
"parser": "vue-eslint-parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 12, "ecmaVersion": 12,
"sourceType": "module" "sourceType": "module",
"parser": {
"js": "espree"
}
}, },
"rules": { "rules": {
"vue/multi-word-component-names": "off",
"vue/require-default-prop": "off",
"vue/no-v-html": "warn",
"no-tabs": "off",
"no-mixed-spaces-and-tabs": "off"
} }
} }

View File

@ -4,7 +4,7 @@ endef
codestyle: codestyle:
npm install npm install
npx eslint --fix main.js js/* npx eslint --fix main.js js/* resources/vue
npx stylelint style.css npx stylelint style.css
clean: clean:

View File

@ -11,24 +11,25 @@
"dependencies": { "dependencies": {
"@connectrpc/connect": "^2.1.2", "@connectrpc/connect": "^2.1.2",
"@connectrpc/connect-web": "^2.1.2", "@connectrpc/connect-web": "^2.1.2",
"@hugeicons/core-free-icons": "^4.2.1", "@hugeicons/core-free-icons": "^4.2.2",
"@hugeicons/vue": "^1.0.6", "@hugeicons/vue": "^1.0.7",
"@vitejs/plugin-vue": "^6.0.7", "@vitejs/plugin-vue": "^6.0.7",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0", "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2", "iconify-icon": "^3.0.2",
"picocrank": "^1.17.0", "picocrank": "^1.18.2",
"standard": "^17.1.2", "standard": "^17.1.2",
"unplugin-vue-components": "^32.1.0", "unplugin-vue-components": "^32.1.0",
"vite": "^8.0.16", "vite": "^8.1.3",
"vue": "^3.5.38", "vue": "^3.5.39",
"vue-i18n": "^11.4.6", "vue-i18n": "^11.4.6",
"vue-router": "^5.1.0" "vue-router": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {
"eslint-plugin-vue": "^10.9.2",
"process": "^0.11.10", "process": "^0.11.10",
"stylelint": "^17.13.0", "stylelint": "^17.14.0",
"stylelint-config-standard": "^40.0.0" "stylelint-config-standard": "^40.0.0"
}, },
"engines": { "engines": {
@ -36,13 +37,13 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.29.0", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.28.5", "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0", "js-tokens": "^4.0.0",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
@ -409,20 +410,20 @@
} }
}, },
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/wasi-threads": "1.2.1", "@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@ -430,9 +431,9 @@
} }
}, },
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@ -941,15 +942,15 @@
} }
}, },
"node_modules/@hugeicons/core-free-icons": { "node_modules/@hugeicons/core-free-icons": {
"version": "4.2.1", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.2.2.tgz",
"integrity": "sha512-75jYZKYyA9VwS35YRmmGUGzFedbY+Fl0Vxx5FzXR2CGDlIhNRumFeVqaaKoClf2MeYEJwPAVMEL9RwCYtOgnSw==", "integrity": "sha512-fjQW3p6cwoJmwK3oCnV8Z3PC5sHihDvr2jkAHax8cxqp62GaV/D7B/Rnao+JwicW8fmLZUQ6QrzWfoyMFOEQlQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@hugeicons/vue": { "node_modules/@hugeicons/vue": {
"version": "1.0.6", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.7.tgz",
"integrity": "sha512-T1Wbuk2qDdoE2xcY3CDtwMOKewwIYDE7JoMVLeuAV4cbOx5CFtToHCQAmLgVPIFGGZUsV2nN52lBkts9upvePw==", "integrity": "sha512-oUE0ztUNYbsMFpMgbV3bsxQBObNkeouomUThFuFPzYC5eV++VTYonStsltJu2MoLMkxV8mzpoVvIbuMPBZSN0w==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"vue": "^2.6.0 || ^3.0.0" "vue": "^2.6.0 || ^3.0.0"
@ -1110,13 +1111,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.5", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@tybys/wasm-util": "^0.10.2" "@tybys/wasm-util": "^0.10.3"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
@ -1160,18 +1161,18 @@
} }
}, },
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.133.0", "version": "0.139.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/Boshen" "url": "https://github.com/sponsors/Boshen"
} }
}, },
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1185,9 +1186,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-arm64": { "node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1201,9 +1202,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-x64": { "node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1217,9 +1218,9 @@
} }
}, },
"node_modules/@rolldown/binding-freebsd-x64": { "node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1233,9 +1234,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm-gnueabihf": { "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1249,9 +1250,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-gnu": { "node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1265,9 +1266,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-musl": { "node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1281,9 +1282,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-ppc64-gnu": { "node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -1297,9 +1298,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-s390x-gnu": { "node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -1313,9 +1314,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-gnu": { "node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1329,9 +1330,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-musl": { "node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1345,9 +1346,9 @@
} }
}, },
"node_modules/@rolldown/binding-openharmony-arm64": { "node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1361,27 +1362,27 @@
} }
}, },
"node_modules/@rolldown/binding-wasm32-wasi": { "node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/core": "1.10.0", "@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.10.0", "@emnapi/runtime": "1.11.1",
"@napi-rs/wasm-runtime": "^1.1.4" "@napi-rs/wasm-runtime": "^1.1.6"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@rolldown/binding-win32-arm64-msvc": { "node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1395,9 +1396,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-x64-msvc": { "node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1436,15 +1437,31 @@
} }
}, },
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.10.2", "version": "0.10.3",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/@types/jsesc": { "node_modules/@types/jsesc": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
@ -1507,39 +1524,39 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz",
"integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.7", "@babel/parser": "^7.29.7",
"@vue/shared": "3.5.38", "@vue/shared": "3.5.39",
"entities": "^7.0.1", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz",
"integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.38", "@vue/compiler-core": "3.5.39",
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz",
"integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.7", "@babel/parser": "^7.29.7",
"@vue/compiler-core": "3.5.38", "@vue/compiler-core": "3.5.39",
"@vue/compiler-dom": "3.5.38", "@vue/compiler-dom": "3.5.39",
"@vue/compiler-ssr": "3.5.38", "@vue/compiler-ssr": "3.5.39",
"@vue/shared": "3.5.38", "@vue/shared": "3.5.39",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"postcss": "^8.5.15", "postcss": "^8.5.15",
@ -1547,13 +1564,13 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz",
"integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.38", "@vue/compiler-dom": "3.5.39",
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@ -1581,53 +1598,53 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz",
"integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz",
"integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.38", "@vue/reactivity": "3.5.39",
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz",
"integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.38", "@vue/reactivity": "3.5.39",
"@vue/runtime-core": "3.5.38", "@vue/runtime-core": "3.5.39",
"@vue/shared": "3.5.38", "@vue/shared": "3.5.39",
"csstype": "^3.2.3" "csstype": "^3.2.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz",
"integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.5.38", "@vue/compiler-ssr": "3.5.39",
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.5.38" "vue": "3.5.39"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz",
"integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@xterm/addon-fit": { "node_modules/@xterm/addon-fit": {
@ -1949,6 +1966,13 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true,
"license": "ISC"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.14", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
@ -2138,9 +2162,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "9.0.1", "version": "9.0.2",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
"integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2990,6 +3014,51 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/eslint-plugin-vue": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.9.2.tgz",
"integrity": "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^7.1.0",
"semver": "^7.6.3",
"xml-name-validator": "^4.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
"@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"vue-eslint-parser": "^10.3.0"
},
"peerDependenciesMeta": {
"@stylistic/eslint-plugin": {
"optional": true
},
"@typescript-eslint/parser": {
"optional": true
}
}
},
"node_modules/eslint-plugin-vue/node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "7.2.2", "version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@ -4897,6 +4966,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -5186,19 +5268,19 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picocrank": { "node_modules/picocrank": {
"version": "1.17.0", "version": "1.18.2",
"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.17.0.tgz", "resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.18.2.tgz",
"integrity": "sha512-EIUSI26elt6ulloN74CT2sX5tBxS2wjdV4A+eXHYNtHbH0hM2iKkYzpKKWOEr+yUTfrvjah6VuqieedFw/9R0w==", "integrity": "sha512-YR1Y00KnEPjH6OCrijSxzTHoyHLhKrsPhkA7ubOkbvcUuD7wtVKbRxfPkjAkA838dGymjoG//iFca2oLnNT5tg==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@hugeicons/core-free-icons": "^4.1.1", "@hugeicons/core-free-icons": "^4.2.2",
"@hugeicons/vue": "^1.0.5", "@hugeicons/vue": "^1.0.7",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.7",
"femtocrank": "^2.5.0", "femtocrank": "^2.5.0",
"unplugin-vue-components": "^32.0.0", "unplugin-vue-components": "^32.1.0",
"vite": "^8.0.10", "vite": "^8.1.2",
"vue": "^3.5.33", "vue": "^3.5.39",
"vue-router": "^5.0.6" "vue-router": "^5.1.0"
} }
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@ -5318,9 +5400,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.15", "version": "8.5.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -5373,9 +5455,9 @@
} }
}, },
"node_modules/postcss-selector-parser": { "node_modules/postcss-selector-parser": {
"version": "7.1.1", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5620,12 +5702,12 @@
} }
}, },
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.0.3", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@oxc-project/types": "=0.133.0", "@oxc-project/types": "=0.139.0",
"@rolldown/pluginutils": "^1.0.0" "@rolldown/pluginutils": "^1.0.0"
}, },
"bin": { "bin": {
@ -5635,21 +5717,21 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-android-arm64": "1.1.5",
"@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.1.5",
"@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-darwin-x64": "1.1.5",
"@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.1.5",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
"@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.1.5",
"@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.1.5",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
"@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.1.5",
"@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.1.5",
"@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.1.5",
"@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.1.5",
"@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.1.5",
"@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.1.5",
"@rolldown/binding-win32-x64-msvc": "1.0.3" "@rolldown/binding-win32-x64-msvc": "1.1.5"
} }
}, },
"node_modules/run-parallel": { "node_modules/run-parallel": {
@ -6151,9 +6233,9 @@
} }
}, },
"node_modules/stylelint": { "node_modules/stylelint": {
"version": "17.13.0", "version": "17.14.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.13.0.tgz", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.0.tgz",
"integrity": "sha512-G1WYzMerp7ihOaIe9VJCHLt12MoAD2QLf1AFerYP37+BCRBUK5UCpq8e/mN+zCIaJPKQcaxhE4WlPmqdiOx/gw==", "integrity": "sha512-8xkHPpdqYryeIsOgfsYTmr6cIeC4nLYWk5S8BPxpodq8mIuepggkMljsHewWfuAjj/+qpRKou2QerhjMH3iasg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -6169,13 +6251,13 @@
"dependencies": { "dependencies": {
"@csstools/css-calc": "^3.2.1", "@csstools/css-calc": "^3.2.1",
"@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-syntax-patches-for-csstree": "^1.1.4", "@csstools/css-syntax-patches-for-csstree": "^1.1.5",
"@csstools/css-tokenizer": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0", "@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-resolve-nested": "^4.0.0",
"@csstools/selector-specificity": "^6.0.0", "@csstools/selector-specificity": "^6.0.0",
"colord": "^2.9.3", "colord": "^2.9.3",
"cosmiconfig": "^9.0.1", "cosmiconfig": "^9.0.2",
"css-functions-list": "^3.3.3", "css-functions-list": "^3.3.3",
"css-tree": "^3.2.1", "css-tree": "^3.2.1",
"debug": "^4.4.3", "debug": "^4.4.3",
@ -6195,7 +6277,7 @@
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"postcss": "^8.5.15", "postcss": "^8.5.15",
"postcss-safe-parser": "^7.0.1", "postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^7.1.1", "postcss-selector-parser": "^7.1.4",
"postcss-value-parser": "^4.2.0", "postcss-value-parser": "^4.2.0",
"string-width": "^8.2.1", "string-width": "^8.2.1",
"supports-hyperlinks": "^4.4.0", "supports-hyperlinks": "^4.4.0",
@ -6790,15 +6872,15 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "8.0.16", "version": "8.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lightningcss": "^1.32.0", "lightningcss": "^1.32.0",
"picomatch": "^4.0.4", "picomatch": "^4.0.4",
"postcss": "^8.5.15", "postcss": "^8.5.16",
"rolldown": "1.0.3", "rolldown": "~1.1.3",
"tinyglobby": "^0.2.17" "tinyglobby": "^0.2.17"
}, },
"bin": { "bin": {
@ -6815,7 +6897,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0", "@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.18", "@vitejs/devtools": "^0.3.0",
"esbuild": "^0.27.0 || ^0.28.0", "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"less": "^4.0.0", "less": "^4.0.0",
@ -6879,16 +6961,16 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.38", "version": "3.5.39",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz",
"integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.38", "@vue/compiler-dom": "3.5.39",
"@vue/compiler-sfc": "3.5.38", "@vue/compiler-sfc": "3.5.39",
"@vue/runtime-dom": "3.5.38", "@vue/runtime-dom": "3.5.39",
"@vue/server-renderer": "3.5.38", "@vue/server-renderer": "3.5.39",
"@vue/shared": "3.5.38" "@vue/shared": "3.5.39"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -6899,6 +6981,98 @@
} }
} }
}, },
"node_modules/vue-eslint-parser": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz",
"integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.0",
"eslint-scope": "^8.2.0 || ^9.0.0",
"eslint-visitor-keys": "^4.2.0 || ^5.0.0",
"espree": "^10.3.0 || ^11.0.0",
"esquery": "^1.6.0",
"semver": "^7.6.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0"
}
},
"node_modules/vue-eslint-parser/node_modules/eslint-scope": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@types/esrecurse": "^4.3.1",
"@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/vue-eslint-parser/node_modules/espree": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^5.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/vue-eslint-parser/node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/vue-i18n": { "node_modules/vue-i18n": {
"version": "11.4.6", "version": "11.4.6",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz",
@ -7145,6 +7319,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/xml-name-validator": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12"
}
},
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",

View File

@ -5,8 +5,9 @@
"repository": "https://github.com/OliveTin/OliveTin", "repository": "https://github.com/OliveTin/OliveTin",
"source": "index.html", "source": "index.html",
"devDependencies": { "devDependencies": {
"eslint-plugin-vue": "^10.9.2",
"process": "^0.11.10", "process": "^0.11.10",
"stylelint": "^17.13.0", "stylelint": "^17.14.0",
"stylelint-config-standard": "^40.0.0" "stylelint-config-standard": "^40.0.0"
}, },
"scripts": { "scripts": {
@ -24,18 +25,18 @@
"dependencies": { "dependencies": {
"@connectrpc/connect": "^2.1.2", "@connectrpc/connect": "^2.1.2",
"@connectrpc/connect-web": "^2.1.2", "@connectrpc/connect-web": "^2.1.2",
"@hugeicons/core-free-icons": "^4.2.1", "@hugeicons/core-free-icons": "^4.2.2",
"@hugeicons/vue": "^1.0.6", "@hugeicons/vue": "^1.0.7",
"@vitejs/plugin-vue": "^6.0.7", "@vitejs/plugin-vue": "^6.0.7",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0", "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2", "iconify-icon": "^3.0.2",
"picocrank": "^1.17.0", "picocrank": "^1.18.2",
"standard": "^17.1.2", "standard": "^17.1.2",
"unplugin-vue-components": "^32.1.0", "unplugin-vue-components": "^32.1.0",
"vite": "^8.0.16", "vite": "^8.1.3",
"vue": "^3.5.38", "vue": "^3.5.39",
"vue-i18n": "^11.4.6", "vue-i18n": "^11.4.6",
"vue-router": "^5.1.0" "vue-router": "^5.1.0"
}, },

View File

@ -1,34 +1,71 @@
<template> <template>
<div :id="`actionButton-${bindingId}`" role="none" class="action-button" @contextmenu.prevent="openActionDetails"> <div
:id="`actionButton-${bindingId}`"
role="none"
class="action-button"
@contextmenu.prevent="openActionDetails"
>
<span <span
v-if="showExecutionIndicator" v-if="showExecutionIndicator"
class="execution-indicator" class="execution-indicator"
:class="executionIndicatorClass" :class="executionIndicatorClass"
:title="executionIndicatorTitle" :title="executionIndicatorTitle"
aria-hidden="true" aria-hidden="true"
></span> />
<button :id="`actionButtonInner-${bindingId}`" :title="title" :disabled="!canExec || isDisabled" <button
:class="combinedClasses" @click="handleClick"> :id="`actionButtonInner-${bindingId}`"
:title="title"
<div v-if="showNavigateOnStartIcons" class="navigate-on-start-container"> :disabled="!canExec || isDisabled"
<div v-if="navigateOnStart == 'pop'" class="navigate-on-start" title="Opens a popup dialog on start"> :class="combinedClasses"
@click="handleClick"
>
<div
v-if="showNavigateOnStartIcons"
class="navigate-on-start-container"
>
<div
v-if="navigateOnStart == 'pop'"
class="navigate-on-start"
title="Opens a popup dialog on start"
>
<HugeiconsIcon :icon="ComputerTerminal01Icon" /> <HugeiconsIcon :icon="ComputerTerminal01Icon" />
</div> </div>
<div v-if="navigateOnStart == 'arg'" class="navigate-on-start" title="Opens an argument form on start"> <div
v-if="navigateOnStart == 'arg'"
class="navigate-on-start"
title="Opens an argument form on start"
>
<HugeiconsIcon :icon="TypeCursorIcon" /> <HugeiconsIcon :icon="TypeCursorIcon" />
</div> </div>
<div v-if="navigateOnStart == 'hist'" class="navigate-on-start" title="Opens action execution history on start"> <div
v-if="navigateOnStart == 'hist'"
class="navigate-on-start"
title="Opens action execution history on start"
>
<HugeiconsIcon :icon="WorkHistoryIcon" /> <HugeiconsIcon :icon="WorkHistoryIcon" />
</div> </div>
<div v-if="navigateOnStart == ''" class="navigate-on-start" title="Run in the background"> <div
v-if="navigateOnStart == ''"
class="navigate-on-start"
title="Run in the background"
>
<HugeiconsIcon :icon="WorkoutRunIcon" /> <HugeiconsIcon :icon="WorkoutRunIcon" />
</div> </div>
</div> </div>
<ActionIconGlyph class="icon" :glyph="actionGlyph" /> <ActionIconGlyph
<span class="title" aria-live="polite">{{ displayTitle }} class="icon"
:glyph="actionGlyph"
/>
<span
class="title"
aria-live="polite"
>{{ displayTitle }}
</span> </span>
<span v-if="rateLimitMessage" class="rate-limit-message">{{ rateLimitMessage }}</span> <span
v-if="rateLimitMessage"
class="rate-limit-message"
>{{ rateLimitMessage }}</span>
</button> </button>
</div> </div>
</template> </template>
@ -84,7 +121,6 @@ const displayTitle = ref('')
// State // State
const isDisabled = ref(false) const isDisabled = ref(false)
const showArgumentForm = ref(false)
// Rate limiting // Rate limiting
const rateLimitExpires = ref(0) const rateLimitExpires = ref(0)

View File

@ -1,68 +1,141 @@
<template> <template>
<Header :title="pageTitle" :logoUrl="logoUrl" @toggleSidebar="toggleSidebar" :sidebarEnabled="sidebarEnabled" :topBarEnabled="topbarEnabled" :navigation="navigation"> <Header
:title="pageTitle"
:logo-url="logoUrl"
:sidebar-enabled="sidebarEnabled"
:top-bar-enabled="topbarEnabled"
:navigation="navigation"
@toggle-sidebar="toggleSidebar"
>
<template #toolbar> <template #toolbar>
<div id="banner" v-if="bannerMessage" :style="bannerCss"> <div
v-if="bannerMessage"
id="banner"
:style="bannerCss"
>
<p>{{ bannerMessage }}</p> <p>{{ bannerMessage }}</p>
</div> </div>
</template> </template>
<template #user-info> <template #user-info>
<ConnectionBanner /> <ConnectionBanner />
<div class="flex-row user-info" style="gap: .5em;"> <div
<span id="link-login" v-if="!isLoggedIn && showLoginLink"><router-link to="/login">{{ t('login-button') }}</router-link></span> class="flex-row user-info"
<router-link v-else to="/user" class="user-link" v-if="isLoggedIn"> style="gap: .5em;"
>
<span
v-if="!isLoggedIn && showLoginLink"
id="link-login"
><router-link to="/login">{{ t('login-button') }}</router-link></span>
<router-link
v-else-if="isLoggedIn"
to="/user"
class="user-link"
>
<span id="username-text">{{ username }}</span> <span id="username-text">{{ username }}</span>
</router-link> </router-link>
<HugeiconsIcon :icon="UserCircle02Icon" width = "1.5em" height = "1.5em" v-if="isLoggedIn" /> <HugeiconsIcon
v-if="isLoggedIn"
:icon="UserCircle02Icon"
width="1.5em"
height="1.5em"
/>
</div> </div>
</template> </template>
</Header> </Header>
<div id="layout"> <div id="layout">
<Navigation ref="navigation"> <Navigation ref="navigation">
<Sidebar ref="sidebar" id = "mainnav" v-if="sidebarEnabled && showNavigation"/> <Sidebar
v-if="sidebarEnabled && showNavigation"
id="mainnav"
ref="sidebar"
/>
</Navigation> </Navigation>
<div id="content" initial-martial-complete="{{ hasLoaded }}"> <div
id="content"
:initial-martial-complete="hasLoaded"
>
<main title="Main content"> <main title="Main content">
<router-view :key="$route.fullPath" /> <router-view :key="$route.fullPath" />
</main> </main>
<footer title="footer" v-if="showFooter"> <footer
v-if="showFooter"
title="footer"
>
<p> <p>
<img title="application icon" :src="logoUrl" alt="OliveTin logo" style="height: 1em;" class="logo" /> <img
title="application icon"
:src="logoUrl"
alt="OliveTin logo"
style="height: 1em;"
class="logo"
>
OliveTin <span v-if="showVersionNumber">{{ currentVersion }}</span> OliveTin <span v-if="showVersionNumber">{{ currentVersion }}</span>
</p> </p>
<p> <p>
<span> <span>
<a href="https://docs.olivetin.app" target="_new">{{ t('docs') }}</a> <a
href="https://docs.olivetin.app"
target="_new"
>{{ t('docs') }}</a>
</span> </span>
<span> <span>
<a href="https://github.com/OliveTin/OliveTin/issues/new/choose" target="_new">{{ t('raise-issue') }}</a> <a
href="https://github.com/OliveTin/OliveTin/issues/new/choose"
target="_new"
>{{ t('raise-issue') }}</a>
</span> </span>
<span> <span>
<a href="#" @click.prevent="openLanguageDialog">{{ currentLanguageName }}</a> <a
href="#"
@click.prevent="openLanguageDialog"
>{{ currentLanguageName }}</a>
</span> </span>
<span v-if="availableThemes.length > 1"> <span v-if="availableThemes.length > 1">
<a href="#" @click.prevent="openThemeDialog">{{ currentThemeName }}</a> <a
href="#"
@click.prevent="openThemeDialog"
>{{ currentThemeName }}</a>
</span> </span>
</p> </p>
<p v-if="showVersionNumber"> <p v-if="showVersionNumber">
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a> <a
id="available-version"
href="http://olivetin.app"
target="_blank"
hidden
>?</a>
</p> </p>
</footer> </footer>
</div> </div>
</div> </div>
<dialog ref="languageDialog" class="language-dialog" @click="handleLanguageDialogClick"> <dialog
<div class="dialog-content" @click.stop> ref="languageDialog"
class="language-dialog"
@click="handleLanguageDialogClick"
>
<div
class="dialog-content"
@click.stop
>
<h2>{{ t('language-dialog.title') }}</h2> <h2>{{ t('language-dialog.title') }}</h2>
<select v-model="selectedLanguage" @change="changeLanguage" class="language-select"> <select
<option v-for="(name, code) in availableLanguages" :key="code" :value="code"> v-model="selectedLanguage"
class="language-select"
@change="changeLanguage"
>
<option
v-for="(name, code) in availableLanguages"
:key="code"
:value="code"
>
{{ code === 'auto' ? name : `${name} (${code})` }} {{ code === 'auto' ? name : `${name} (${code})` }}
</option> </option>
</select> </select>
@ -72,55 +145,74 @@
<span v-else>{{ t('language-dialog.not-available') }}</span> <span v-else>{{ t('language-dialog.not-available') }}</span>
</p> </p>
<div class="dialog-buttons"> <div class="dialog-buttons">
<button @click="closeLanguageDialog">{{ t('language-dialog.close') }}</button> <button @click="closeLanguageDialog">
{{ t('language-dialog.close') }}
</button>
</div> </div>
</div> </div>
</dialog> </dialog>
<dialog ref="themeDialog" class="theme-dialog" @click="handleThemeDialogClick"> <dialog
<div class="dialog-content" @click.stop> ref="themeDialog"
class="theme-dialog"
@click="handleThemeDialogClick"
>
<div
class="dialog-content"
@click.stop
>
<h2>{{ t('theme-dialog.title') }}</h2> <h2>{{ t('theme-dialog.title') }}</h2>
<select v-model="selectedTheme" @change="changeTheme" class="language-select"> <select
<option value="">{{ t('theme-dialog.default') }}</option> v-model="selectedTheme"
<option v-for="theme in availableThemes" :key="theme" :value="theme"> class="language-select"
@change="changeTheme"
>
<option value="">
{{ t('theme-dialog.default') }}
</option>
<option
v-for="theme in availableThemes"
:key="theme"
:value="theme"
>
{{ theme }} {{ theme }}
</option> </option>
</select> </select>
<div class="dialog-buttons"> <div class="dialog-buttons">
<button @click="closeThemeDialog">{{ t('theme-dialog.close') }}</button> <button @click="closeThemeDialog">
{{ t('theme-dialog.close') }}
</button>
</div> </div>
</div> </div>
</dialog> </dialog>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
import Sidebar from 'picocrank/vue/components/Sidebar.vue'; import Sidebar from 'picocrank/vue/components/Sidebar.vue'
import Navigation from 'picocrank/vue/components/Navigation.vue'; import Navigation from 'picocrank/vue/components/Navigation.vue'
import Header from 'picocrank/vue/components/Header.vue'; import Header from 'picocrank/vue/components/Header.vue'
import ConnectionBanner from './components/ConnectionBanner.vue'; import ConnectionBanner from './components/ConnectionBanner.vue'
import { connectEventStreamIfNeeded } from '../../js/websocket.js'; import { connectEventStreamIfNeeded } from '../../js/websocket.js'
import { HugeiconsIcon } from '@hugeicons/vue' import { HugeiconsIcon } from '@hugeicons/vue'
import { Menu01Icon } from '@hugeicons/core-free-icons' import { UserCircle02Icon, DashboardSquare01Icon } from '@hugeicons/core-free-icons'
import { UserCircle02Icon } from '@hugeicons/core-free-icons' import logoUrl from '../../OliveTinLogo.png'
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons' import { useI18n } from 'vue-i18n'
import logoUrl from '../../OliveTinLogo.png'; import combinedTranslations from '../../../lang/combined_output.json'
import { useI18n } from 'vue-i18n'; const { t } = useI18n()
import combinedTranslations from '../../../lang/combined_output.json';
const { t, locale } = useI18n();
const router = useRouter(); const router = useRouter()
const sidebar = ref(null); const sidebar = ref(null)
const navigation = ref(null); const navigation = ref(null)
const username = ref('notset'); const username = ref('notset')
const isLoggedIn = ref(false); const isLoggedIn = ref(false)
const currentVersion = ref('?'); const currentVersion = ref('?')
const pageTitle = ref('OliveTin'); const pageTitle = ref('OliveTin')
const bannerMessage = ref(''); const bannerMessage = ref('')
const bannerCss = ref(''); const bannerCss = ref('')
const hasLoaded = ref(false); const hasLoaded = ref(false)
const showFooter = ref(true) const showFooter = ref(true)
const showNavigation = ref(true) const showNavigation = ref(true)
const showLogs = ref(true) const showLogs = ref(true)
@ -144,8 +236,8 @@ const selectedTheme = ref(themePreference.value)
// Available languages with display names // Available languages with display names
const availableLanguages = { const availableLanguages = {
'auto': 'Browser Language', auto: 'Browser Language',
'en': 'English', en: 'English',
'de-DE': 'Deutsch', 'de-DE': 'Deutsch',
'es-ES': 'Español', 'es-ES': 'Español',
'it-IT': 'Italiano', 'it-IT': 'Italiano',
@ -155,7 +247,7 @@ const availableLanguages = {
// Computed property to get current language display name // Computed property to get current language display name
const currentLanguageName = computed(() => { const currentLanguageName = computed(() => {
if (languagePreference.value === 'auto') { if (languagePreference.value === 'auto') {
return availableLanguages['auto'] return availableLanguages.auto
} }
return availableLanguages[languagePreference.value] || languagePreference.value return availableLanguages[languagePreference.value] || languagePreference.value
@ -263,7 +355,7 @@ function renderNavigation() {
name: rootDashboard, name: rootDashboard,
title: rootDashboard, title: rootDashboard,
path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`, path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
icon: DashboardSquare01Icon, icon: DashboardSquare01Icon
}) })
} }
@ -370,7 +462,7 @@ function applyTheme() {
if (themePreference.value && themePreference.value !== '') { if (themePreference.value && themePreference.value !== '') {
themeStyle.textContent = `@import url('/custom-webui/themes/${themePreference.value}/theme.css') layer(theme);` themeStyle.textContent = `@import url('/custom-webui/themes/${themePreference.value}/theme.css') layer(theme);`
} else { } else {
themeStyle.textContent = `@import url('/theme.css') layer(theme);` themeStyle.textContent = '@import url(\'/theme.css\') layer(theme);'
} }
} }

View File

@ -1,34 +1,84 @@
<template> <template>
<section v-if="!dashboard && !initError" style = "text-align: center; padding: 2em;"> <section
<HugeiconsIcon :icon="Loading03Icon" width="3em" height="3em" style="animation: spin 1s linear infinite;" /> v-if="!dashboard && !initError"
style="text-align: center; padding: 2em;"
>
<HugeiconsIcon
:icon="Loading03Icon"
width="3em"
height="3em"
style="animation: spin 1s linear infinite;"
/>
<p>Loading dashboard...</p> <p>Loading dashboard...</p>
<p style="color: var(--fg2);">{{ loadingTime }}s</p> <p style="color: var(--fg2);">
{{ loadingTime }}s
</p>
</section> </section>
<section v-if="initError" style="text-align: center; padding: 2em;" class = "bad"> <section
<h2 style="color: var(--error);">Initialization Failed</h2> v-if="initError"
style="text-align: center; padding: 2em;"
class="bad"
>
<h2 style="color: var(--error);">
Initialization Failed
</h2>
<p>{{ initError }}</p> <p>{{ initError }}</p>
<p style="color: var(--fg2);">Please check your configuration and try again.</p> <p style="color: var(--fg2);">
Please check your configuration and try again.
</p>
</section> </section>
<template v-else-if="dashboard"> <template v-else-if="dashboard">
<section v-if="dashboard.contents.length == 0"> <section v-if="dashboard.contents.length == 0">
<div class="back-button-container" v-if="isDirectory"> <div
<button @click="goBack" class="back-button"> v-if="isDirectory"
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" /> class="back-button-container"
>
<button
class="back-button"
@click="goBack"
>
<HugeiconsIcon
:icon="ArrowLeftIcon"
width="1.2em"
height="1.2em"
/>
<span>Back</span> <span>Back</span>
</button> </button>
</div> </div>
<h2>{{ dashboard.title }}</h2> <h2>{{ dashboard.title }}</h2>
<p style = "text-align: center" class = "padding">This dashboard is empty.</p> <p
style="text-align: center"
class="padding"
>
This dashboard is empty.
</p>
</section> </section>
<section class="transparent" v-else> <section
<div class="back-button-container" v-if="isDirectory"> v-else
<button @click="goBack" class="back-button"> class="transparent"
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" /> >
<div
v-if="isDirectory"
class="back-button-container"
>
<button
class="back-button"
@click="goBack"
>
<HugeiconsIcon
:icon="ArrowLeftIcon"
width="1.2em"
height="1.2em"
/>
<span>Back</span> <span>Back</span>
</button> </button>
</div> </div>
<div class = "dashboard-row" v-for="component in dashboard.contents" :key="component.title"> <div
v-for="component in dashboard.contents"
:key="component.title"
class="dashboard-row"
>
<h2 v-if="dashboard.title != 'Default'"> <h2 v-if="dashboard.title != 'Default'">
<router-link <router-link
v-if="component.entityType && component.entityKey" v-if="component.entityType && component.entityKey"
@ -39,14 +89,18 @@
entityKey: component.entityKey entityKey: component.entityKey
} }
}" }"
class="entity-link"> class="entity-link"
>
{{ component.title }} {{ component.title }}
</router-link> </router-link>
<span v-else>{{ component.title }}</span> <span v-else>{{ component.title }}</span>
</h2> </h2>
<fieldset :class="component.cssClass"> <fieldset :class="component.cssClass">
<template v-for="subcomponent in component.contents"> <template
v-for="subcomponent in component.contents"
:key="subcomponent.title || subcomponent.type"
>
<DashboardComponent :component="subcomponent" /> <DashboardComponent :component="subcomponent" />
</template> </template>
</fieldset> </fieldset>
@ -118,7 +172,7 @@ async function getDashboard() {
try { try {
const request = { const request = {
title: title, title
} }
if (props.entityType && props.entityKey) { if (props.entityType && props.entityKey) {
@ -279,7 +333,6 @@ fieldset {
place-items: stretch; place-items: stretch;
} }
@keyframes spin { @keyframes spin {
from { from {
transform: rotate(0deg); transform: rotate(0deg);

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
:id="`execution-${executionTrackingId}`" :id="`execution-${trackingId}`"
class="execution-button" class="execution-button"
> >
<button <button
@ -26,6 +26,7 @@ export default {
}, },
data () { data () {
return { return {
trackingId: '',
ellapsed: 0, ellapsed: 0,
isWaiting: true isWaiting: true
} }
@ -40,11 +41,12 @@ export default {
} }
}, },
mounted () { mounted () {
this.trackingId = this.executionTrackingId
this.constructFromJson(this.executionTrackingId) this.constructFromJson(this.executionTrackingId)
}, },
methods: { methods: {
constructFromJson (json) { constructFromJson (json) {
this.executionTrackingId = json this.trackingId = json
this.ellapsed = 0 this.ellapsed = 0
this.isWaiting = true this.isWaiting = true
}, },
@ -55,7 +57,7 @@ export default {
if (window.executionDialog) { if (window.executionDialog) {
window.executionDialog.reset() window.executionDialog.reset()
window.executionDialog.show() window.executionDialog.show()
window.executionDialog.fetchExecutionResult(this.executionTrackingId) window.executionDialog.fetchExecutionResult(this.trackingId)
} }
}, },

View File

@ -1,5 +1,8 @@
<template> <template>
<span v-if="showLimits" class="action-group-limit"> <span
v-if="showLimits"
class="action-group-limit"
>
{{ t('logs.action-group-limits', { concurrent: maxConcurrent, queueSize }) }} {{ t('logs.action-group-limits', { concurrent: maxConcurrent, queueSize }) }}
</span> </span>
</template> </template>

View File

@ -7,8 +7,14 @@
height="1em" height="1em"
class="action-icon-glyph-svg" class="action-icon-glyph-svg"
/> />
<span v-else-if="decodedTextGlyphIsHtml" v-html="decodedTextGlyph"></span> <span
<span v-else v-text="decodedTextGlyph"></span> v-else-if="decodedTextGlyphIsHtml"
v-html="decodedTextGlyph"
/>
<span
v-else
v-text="decodedTextGlyph"
/>
</span> </span>
</template> </template>
@ -22,15 +28,15 @@ const hugeiconsPrefix = 'hugeicons:'
/** Maps config values like hugeicons:CommandLineIcon to Hugeicons icon definitions. */ /** Maps config values like hugeicons:CommandLineIcon to Hugeicons icon definitions. */
const hugeiconsRegistry = { const hugeiconsRegistry = {
CommandLineIcon, CommandLineIcon
} }
const props = defineProps({ const props = defineProps({
glyph: { glyph: {
type: String, type: String,
required: false, required: false,
default: '', default: ''
}, }
}) })
const hugeiconsModel = computed(() => { const hugeiconsModel = computed(() => {

View File

@ -1,9 +1,14 @@
<template> <template>
<div :class="statusClass + ' annotation'"> <div :class="statusClass + ' annotation'">
<router-link v-if="showQueueLink" to="/logs/queue" class="queue-status-link">{{ statusText }}</router-link> <router-link
v-if="showQueueLink"
to="/logs/queue"
class="queue-status-link"
>
{{ statusText }}
</router-link>
<span v-else>{{ statusText }}</span><span>{{ exitCodeText }}</span> <span v-else>{{ statusText }}</span><span>{{ exitCodeText }}</span>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -110,5 +115,4 @@ const statusClass = computed(() => {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>

View File

@ -1,14 +1,50 @@
<template> <template>
<div id="breadcrumbs"> <div id="breadcrumbs">
<template v-for="(link, index) in links" :key="link.name"> <template
<router-link :to="link.href">{{ link.name }}</router-link> v-for="(link, index) in links"
<span v-if="index < links.length - 1" class="separator"> :key="link.name"
>
<router-link :to="link.href">
{{ link.name }}
</router-link>
<span
v-if="index < links.length - 1"
class="separator"
>
&raquo; &raquo;
</span> </span>
</template> </template>
</div> </div>
</template> </template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const links = ref([])
watch(() => route.matched, (matched) => {
links.value = []
matched.forEach((record) => {
if (!record) return
if (record.meta && record.meta.breadcrumb) {
record.meta.breadcrumb.forEach((item) => {
links.value.push({
name: item.name,
href: item.href || record.path || '/'
})
})
} else if (record.name) {
links.value.push({
name: record.name,
href: record.path || '/'
})
}
})
}, { immediate: true })
</script>
<style scoped> <style scoped>
span { span {
color: #bbb; color: #bbb;
@ -26,34 +62,3 @@ a:hover {
} }
</style> </style>
<script setup>
import { ref } from 'vue';
import { watch } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const links = ref([]);
watch(() => route.matched, (matched) => {
links.value = [];
matched.forEach((record) => {
if (!record) return;
if (record.meta && record.meta.breadcrumb) {
record.meta.breadcrumb.forEach((item) => {
links.value.push({
name: item.name,
href: item.href || record.path || '/'
});
});
} else if (record.name) {
links.value.push({
name: record.name,
href: record.path || '/'
});
}
});
}, { immediate: true });
</script>

View File

@ -1,15 +1,28 @@
<template> <template>
<div class="choice-checklist" :id="wrapperId"> <div
:id="wrapperId"
class="choice-checklist"
>
<div class="choice-checklist-controls"> <div class="choice-checklist-controls">
<button type="button" class="choice-checklist-control" @click="selectAll"> <button
type="button"
class="choice-checklist-control"
@click="selectAll"
>
Select all Select all
</button> </button>
<button type="button" class="choice-checklist-control" @click="selectNone"> <button
type="button"
class="choice-checklist-control"
@click="selectNone"
>
Select none Select none
</button> </button>
</div> </div>
<fieldset class="choice-checklist-fieldset"> <fieldset class="choice-checklist-fieldset">
<legend class="visually-hidden">{{ label || name }}</legend> <legend class="visually-hidden">
{{ label || name }}
</legend>
<label <label
v-for="(choice, index) in choices" v-for="(choice, index) in choices"
:key="choice.value" :key="choice.value"
@ -21,7 +34,7 @@
type="checkbox" type="checkbox"
:checked="isSelected(choice.value)" :checked="isSelected(choice.value)"
@change="handleToggle(choice.value)" @change="handleToggle(choice.value)"
/> >
<span>{{ choiceLabel(choice) }}</span> <span>{{ choiceLabel(choice) }}</span>
</label> </label>
</fieldset> </fieldset>
@ -34,7 +47,7 @@
:required="required" :required="required"
tabindex="-1" tabindex="-1"
aria-hidden="true" aria-hidden="true"
/> >
</div> </div>
</template> </template>

View File

@ -1,8 +1,11 @@
<template> <template>
<div class="choice-combobox" ref="rootRef"> <div
ref="rootRef"
class="choice-combobox"
>
<input <input
ref="searchInputRef"
:id="id" :id="id"
ref="searchInputRef"
type="text" type="text"
class="choice-combobox-input" class="choice-combobox-input"
role="combobox" role="combobox"
@ -17,12 +20,12 @@
@input="handleSearchInput" @input="handleSearchInput"
@keydown="handleKeydown" @keydown="handleKeydown"
@blur="handleBlur" @blur="handleBlur"
/> >
<input <input
:name="name" :name="name"
type="hidden" type="hidden"
:value="modelValue" :value="modelValue"
/> >
<ul <ul
v-if="isOpen && filteredChoices.length > 0" v-if="isOpen && filteredChoices.length > 0"
:id="listboxId" :id="listboxId"
@ -44,7 +47,10 @@
{{ choiceLabel(choice) }} {{ choiceLabel(choice) }}
</li> </li>
</ul> </ul>
<div v-else-if="isOpen && query" class="choice-combobox-list choice-combobox-empty"> <div
v-else-if="isOpen && query"
class="choice-combobox-list choice-combobox-empty"
>
No matching options No matching options
</div> </div>
</div> </div>

View File

@ -1,8 +1,20 @@
<template> <template>
<span id="connection-banner" v-if="connectionState.showDisconnectedBanner" class="inline-notification critical user-info-connection"> <span
<span class="connection-banner-sr-only" role="status">{{ staticAnnouncement }}</span> v-if="connectionState.showDisconnectedBanner"
id="connection-banner"
class="inline-notification critical user-info-connection"
>
<span
class="connection-banner-sr-only"
role="status"
>{{ staticAnnouncement }}</span>
<span aria-hidden="true"> <span aria-hidden="true">
<a :href="websocketDocsUrl" target="_blank" rel="noopener noreferrer" class="connection-banner-link">{{ linkText }}</a>{{ bannerSuffix }} <a
:href="websocketDocsUrl"
target="_blank"
rel="noopener noreferrer"
class="connection-banner-link"
>{{ linkText }}</a>{{ bannerSuffix }}
</span> </span>
</span> </span>
</template> </template>

View File

@ -1,14 +1,31 @@
<template> <template>
<ActionButton v-if="component.type == 'link'" :actionData="component.action" :cssClass="component.cssClass" :key="component.title" /> <ActionButton
v-if="component.type == 'link'"
:key="component.title"
:action-data="component.action"
:css-class="component.cssClass"
/>
<DashboardComponentDirectory v-else-if="component.type == 'directory'" :component="component" /> <DashboardComponentDirectory
v-else-if="component.type == 'directory'"
:component="component"
/>
<DashboardComponentDisplay v-else-if="component.type == 'display'" :component="component" /> <DashboardComponentDisplay
v-else-if="component.type == 'display'"
:component="component"
/>
<DashboardComponentMostRecentExecution v-else-if="component.type == 'stdout-most-recent-execution'" :component="component" /> <DashboardComponentMostRecentExecution
v-else-if="component.type == 'stdout-most-recent-execution'"
:component="component"
/>
<template v-else-if="component.type == 'fieldset'"> <template v-else-if="component.type == 'fieldset'">
<template v-for="subcomponent in component.contents" :key="subcomponent.title"> <template
v-for="subcomponent in component.contents"
:key="subcomponent.title"
>
<DashboardComponent :component="subcomponent" /> <DashboardComponent :component="subcomponent" />
</template> </template>
</template> </template>
@ -17,7 +34,6 @@
OTHER: {{ component.type }} OTHER: {{ component.type }}
{{ component }} {{ component }}
</div> </div>
</template> </template>
<script setup> <script setup>
@ -26,7 +42,7 @@ import DashboardComponentMostRecentExecution from './DashboardComponentMostRecen
import DashboardComponentDirectory from './DashboardComponentDirectory.vue' import DashboardComponentDirectory from './DashboardComponentDirectory.vue'
import DashboardComponentDisplay from './DashboardComponentDisplay.vue' import DashboardComponentDisplay from './DashboardComponentDisplay.vue'
const props = defineProps({ defineProps({
component: { component: {
type: Object, type: Object,
required: true required: true

View File

@ -1,6 +1,13 @@
<template> <template>
<button class="directory-button" :class="component.cssClass" @click="navigateToDirectory"> <button
<span class="icon" v-html="unicodeIcon"></span> class="directory-button"
:class="component.cssClass"
@click="navigateToDirectory"
>
<span
class="icon"
v-html="unicodeIcon"
/>
<span class="title">{{ component.title }}</span> <span class="title">{{ component.title }}</span>
</button> </button>
</template> </template>

View File

@ -1,11 +1,14 @@
<template> <template>
<div class="display" :class="component.cssClass"> <div
class="display"
:class="component.cssClass"
>
<div v-html="component.title" /> <div v-html="component.title" />
</div> </div>
</template> </template>
<script setup> <script setup>
const props = defineProps({ defineProps({
component: { component: {
type: Object, type: Object,
required: true required: true

View File

@ -1,5 +1,8 @@
<template> <template>
<div class="mre-container" :class="component.cssClass"> <div
class="mre-container"
:class="component.cssClass"
>
<router-link <router-link
v-if="executionTrackingId" v-if="executionTrackingId"
:to="`/logs/${executionTrackingId}`" :to="`/logs/${executionTrackingId}`"
@ -7,7 +10,10 @@
> >
<pre class="mre-output">{{ output }}</pre> <pre class="mre-output">{{ output }}</pre>
</router-link> </router-link>
<pre v-else class="mre-output fg-important">{{ output }}</pre> <pre
v-else
class="mre-output fg-important"
>{{ output }}</pre>
</div> </div>
</template> </template>

View File

@ -3,44 +3,71 @@
<template #title> <template #title>
<span class="section-title-with-icon"> <span class="section-title-with-icon">
Entity: Entity:
<ActionIconGlyph v-if="definition.icon" class="entity-title-icon" :glyph="definition.icon" /> <ActionIconGlyph
v-if="definition.icon"
class="entity-title-icon"
:glyph="definition.icon"
/>
{{ definition.title }} {{ definition.title }}
</span> </span>
</template> </template>
<template v-if="hasTable" #toolbar> <template
v-if="hasTable"
#toolbar
>
<EntityListFilter v-model="searchText" /> <EntityListFilter v-model="searchText" />
</template> </template>
<p v-if="!hasTable">{{ definition.instances.length }} instances.</p> <p v-if="!hasTable">
{{ definition.instances.length }} instances.
</p>
<template v-if="hasTable"> <template v-if="hasTable">
<p v-if="tableError" class="table-error padding" role="alert">{{ tableError }}</p> <p
v-if="tableError"
class="table-error padding"
role="alert"
>
{{ tableError }}
</p>
<EntityInstancesTable <EntityInstancesTable
v-else v-else
v-model:page="currentPage"
v-model:page-size="pageSize"
:instances="tableInstances" :instances="tableInstances"
:properties="definition.properties" :properties="definition.properties"
:total-instances="totalInstances" :total-instances="totalInstances"
v-model:page="currentPage"
v-model:page-size="pageSize"
/> />
</template> </template>
<ul v-else> <ul v-else>
<li v-for="inst in definition.instances" :key="inst.uniqueKey"> <li
v-for="inst in definition.instances"
:key="inst.uniqueKey"
>
<router-link :to="entityDetailsRoute(inst)"> <router-link :to="entityDetailsRoute(inst)">
{{ inst.title }} {{ inst.title }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
<div v-if="usedDashboards.length > 0" :class="{ padding: hasTable }"> <div
v-if="usedDashboards.length > 0"
:class="{ padding: hasTable }"
>
<h3>Used on Dashboards:</h3> <h3>Used on Dashboards:</h3>
<ul> <ul>
<li v-for="dash in usedDashboards" :key="dash"> <li
v-for="dash in usedDashboards"
:key="dash"
>
<template v-if="isEntityDirectory(dash)"> <template v-if="isEntityDirectory(dash)">
{{ getDashboardTitle(dash) }} <span class="entity-directory-label">[Entity Directory]</span> {{ getDashboardTitle(dash) }} <span class="entity-directory-label">[Entity Directory]</span>
</template> </template>
<router-link v-else-if="!dash.includes('entity:')" :to="{ name: 'Dashboard', params: { title: getDashboardTitle(dash) } }"> <router-link
v-else-if="!dash.includes('entity:')"
:to="{ name: 'Dashboard', params: { title: getDashboardTitle(dash) } }"
>
{{ getDashboardTitle(dash) }} {{ getDashboardTitle(dash) }}
</router-link> </router-link>
<span v-else>{{ dash }}</span> <span v-else>{{ dash }}</span>

View File

@ -11,11 +11,14 @@
</template> </template>
</Table> </Table>
<div v-if="totalInstances > 0" class="padding"> <div
v-if="totalInstances > 0"
class="padding"
>
<Pagination <Pagination
:total="totalInstances"
v-model:page="currentPageModel" v-model:page="currentPageModel"
v-model:page-size="pageSizeModel" v-model:page-size="pageSizeModel"
:total="totalInstances"
item-title="entities" item-title="entities"
/> />
</div> </div>

View File

@ -1,19 +1,39 @@
<template> <template>
<label class="input-with-icons"> <label class="input-with-icons">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" aria-hidden="true"> <svg
<path fill="currentColor" xmlns="http://www.w3.org/2000/svg"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" /> width="1em"
height="1em"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill="currentColor"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"
/>
</svg> </svg>
<input <input
:value="modelValue" :value="modelValue"
aria-label="Filter entities" aria-label="Filter entities"
placeholder="Filter entities..." placeholder="Filter entities..."
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
>
<button
title="Clear search filter"
:disabled="!modelValue"
@click="$emit('update:modelValue', '')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill="currentColor"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
/> />
<button title="Clear search filter" :disabled="!modelValue" @click="$emit('update:modelValue', '')">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" aria-hidden="true">
<path fill="currentColor"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" />
</svg> </svg>
</button> </button>
</label> </label>

View File

@ -1,7 +1,10 @@
<template> <template>
<span class="log-action-title"> <span class="log-action-title">
<slot>{{ actionTitle }}</slot> <slot>{{ actionTitle }}</slot>
<span v-if="justification" class="log-justification">— {{ justification }}</span> <span
v-if="justification"
class="log-justification"
>— {{ justification }}</span>
</span> </span>
</template> </template>

View File

@ -2,10 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from './Dashboard.vue' import Dashboard from './Dashboard.vue'
import { Wrench01Icon } from '@hugeicons/core-free-icons' import { Wrench01Icon, LeftToRightListDashIcon, CellsIcon, DashboardSquare01Icon } from '@hugeicons/core-free-icons'
import { LeftToRightListDashIcon } from '@hugeicons/core-free-icons'
import { CellsIcon } from '@hugeicons/core-free-icons'
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
const routes = [ const routes = [
{ {
@ -44,8 +41,8 @@ const routes = [
meta: { meta: {
title: 'Logs Calendar', title: 'Logs Calendar',
breadcrumb: [ breadcrumb: [
{ name: "Logs", href: "/logs" }, { name: 'Logs', href: '/logs' },
{ name: "Calendar" }, { name: 'Calendar' }
] ]
} }
}, },
@ -56,8 +53,8 @@ const routes = [
meta: { meta: {
title: 'Execution Queue', title: 'Execution Queue',
breadcrumb: [ breadcrumb: [
{ name: "Logs", href: "/logs" }, { name: 'Logs', href: '/logs' },
{ name: "Queue" }, { name: 'Queue' }
] ]
} }
}, },
@ -78,8 +75,8 @@ const routes = [
meta: { meta: {
title: 'OliveTin - Entity Details', title: 'OliveTin - Entity Details',
breadcrumb: [ breadcrumb: [
{ name: "Entities", href: "/entities" }, { name: 'Entities', href: '/entities' },
{ name: "Entity Details" } { name: 'Entity Details' }
] ]
} }
}, },
@ -91,8 +88,8 @@ const routes = [
meta: { meta: {
title: 'Execution', title: 'Execution',
breadcrumb: [ breadcrumb: [
{ name: "Logs", href: "/logs" }, { name: 'Logs', href: '/logs' },
{ name: "Execution" }, { name: 'Execution' }
] ]
} }
}, },
@ -104,8 +101,8 @@ const routes = [
meta: { meta: {
title: 'Action Details', title: 'Action Details',
breadcrumb: [ breadcrumb: [
{ name: "Actions", href: "/" }, { name: 'Actions', href: '/' },
{ name: "Action Details" }, { name: 'Action Details' }
] ]
} }
}, },
@ -117,8 +114,8 @@ const routes = [
meta: { meta: {
title: 'Execution conditions', title: 'Execution conditions',
breadcrumb: [ breadcrumb: [
{ name: "Actions", href: "/" }, { name: 'Actions', href: '/' },
{ name: "Execution conditions" }, { name: 'Execution conditions' }
] ]
} }
}, },
@ -168,7 +165,7 @@ const router = createRouter({
router.beforeEach((to) => { router.beforeEach((to) => {
if (to.meta && to.meta.title) { if (to.meta && to.meta.title) {
const pageTitle = window.initResponse?.pageTitle || 'OliveTin' const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = to.meta.title + " - " + pageTitle document.title = to.meta.title + ' - ' + pageTitle
} }
}) })

View File

@ -3,7 +3,11 @@
<template #title> <template #title>
<span class="section-title-with-icon"> <span class="section-title-with-icon">
Action Details: Action Details:
<ActionIconGlyph v-if="action" class="action-title-icon" :glyph="action.icon" /> <ActionIconGlyph
v-if="action"
class="action-title-icon"
:glyph="action.icon"
/>
{{ actionTitle }} {{ actionTitle }}
</span> </span>
</template> </template>
@ -12,14 +16,19 @@
<button <button
v-for="dashboard in backToDashboards" v-for="dashboard in backToDashboards"
:key="dashboard.path" :key="dashboard.path"
@click="goToDashboard(dashboard.path)"
:title="'Back to ' + dashboard.title" :title="'Back to ' + dashboard.title"
class="button neutral" class="button neutral"
@click="goToDashboard(dashboard.path)"
> >
<HugeiconsIcon :icon="DashboardSquare01Icon" /> <HugeiconsIcon :icon="DashboardSquare01Icon" />
{{ dashboard.title }} {{ dashboard.title }}
</button> </button>
<button v-if="action" @click="startAction" title="Run this action" class="button neutral"> <button
v-if="action"
title="Run this action"
class="button neutral"
@click="startAction"
>
<HugeiconsIcon :icon="WorkoutRunIcon" /> <HugeiconsIcon :icon="WorkoutRunIcon" />
Run Run
</button> </button>
@ -34,7 +43,10 @@
</div> </div>
</template> </template>
<div class = "flex-row padding" v-if="action"> <div
v-if="action"
class="flex-row padding"
>
<div class="fg1"> <div class="fg1">
<dl> <dl>
<dt>Timeout</dt> <dt>Timeout</dt>
@ -42,12 +54,28 @@
<template v-if="actionGroups.length > 0"> <template v-if="actionGroups.length > 0">
<dt> <dt>
<router-link :to="{ name: 'LogsQueue' }" class="action-groups-link">Action groups</router-link> <router-link
:to="{ name: 'LogsQueue' }"
class="action-groups-link"
>
Action groups
</router-link>
</dt> </dt>
<dd> <dd>
<ul class="action-group-list"> <ul class="action-group-list">
<li v-for="group in actionGroups" :key="group.name" class="action-group-row"> <li
<router-link :to="{ name: 'LogsQueue' }" class="action-groups-link action-group-name">{{ group.name }}</router-link><template v-if="group.maxConcurrent > 0 && group.queueSize > 0"> - </template><ActionGroupLimitsLabel v-for="group in actionGroups"
:key="group.name"
class="action-group-row"
>
<router-link
:to="{ name: 'LogsQueue' }"
class="action-groups-link action-group-name"
>
{{ group.name }}
</router-link><template v-if="group.maxConcurrent > 0 && group.queueSize > 0">
-
</template><ActionGroupLimitsLabel
:max-concurrent="group.maxConcurrent" :max-concurrent="group.maxConcurrent"
:queue-size="group.queueSize" :queue-size="group.queueSize"
/> />
@ -63,15 +91,36 @@
<div style="align-self: start; text-align: right;"> <div style="align-self: start; text-align: right;">
<div class="filter-container"> <div class="filter-container">
<label class="input-with-icons"> <label class="input-with-icons">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<path fill="currentColor" xmlns="http://www.w3.org/2000/svg"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" /> width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"
/>
</svg> </svg>
<input placeholder="Filter current page" v-model="searchText" /> <input
<button title="Clear search filter" :disabled="!searchText" @click="clearSearch"> v-model="searchText"
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> placeholder="Filter current page"
<path fill="currentColor" >
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" /> <button
title="Clear search filter"
:disabled="!searchText"
@click="clearSearch"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
/>
</svg> </svg>
</button> </button>
</label> </label>
@ -91,9 +140,18 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="log in filteredLogs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle"> <tr
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td> v-for="log in filteredLogs"
<td class="duration">{{ formatExecutionDuration(log) }}</td> :key="log.executionTrackingId"
class="log-row"
:title="log.actionTitle"
>
<td class="timestamp">
{{ formatTimestamp(log.datetimeStarted) }}
</td>
<td class="duration">
{{ formatExecutionDuration(log) }}
</td>
<td> <td>
<router-link :to="`/logs/${log.executionTrackingId}`"> <router-link :to="`/logs/${log.executionTrackingId}`">
<LogActionTitle :justification="log.justification"> <LogActionTitle :justification="log.justification">
@ -106,24 +164,47 @@
<span class="annotation-key">User:</span> <span class="annotation-key">User:</span>
<span class="annotation-val">{{ log.user }}</span> <span class="annotation-val">{{ log.user }}</span>
</span> </span>
<span v-if="log.tags && log.tags.length > 0" class="tag-list"> <span
<span v-for="tag in log.tags" :key="tag" class="tag">{{ tag }}</span> v-if="log.tags && log.tags.length > 0"
class="tag-list"
>
<span
v-for="tag in log.tags"
:key="tag"
class="tag"
>{{ tag }}</span>
</span> </span>
</td> </td>
<td class="exit-code"> <td class="exit-code">
<ActionStatusDisplay :logEntry="log" :link-queued-status="true" /> <ActionStatusDisplay
:log-entry="log"
:link-queued-status="true"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class="padding" <Pagination
@page-size-change="handlePageSizeChange" itemTitle="execution logs" /> :page-size="pageSize"
:total="totalCount"
:current-page="currentPage"
:page="currentPage"
class="padding"
item-title="execution logs"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
</div> </div>
<div v-show="logs.length === 0 && !loading" class="empty-state"> <div
v-show="logs.length === 0 && !loading"
class="empty-state"
>
<p>This action has no execution history.</p> <p>This action has no execution history.</p>
<router-link to="/">Return to index</router-link> <router-link to="/">
Return to index
</router-link>
</div> </div>
</Section> </Section>
</template> </template>
@ -181,9 +262,9 @@ async function fetchActionLogs() {
const startOffset = (currentPage.value - 1) * pageSize.value const startOffset = (currentPage.value - 1) * pageSize.value
const args = { const args = {
"actionId": actionId, actionId,
"startOffset": BigInt(startOffset), startOffset: BigInt(startOffset),
"pageSize": BigInt(Number(pageSize.value)), pageSize: BigInt(Number(pageSize.value))
} }
const response = await window.client.getActionLogs(args) const response = await window.client.getActionLogs(args)
@ -207,7 +288,7 @@ async function fetchAction() {
try { try {
const actionId = route.params.actionId const actionId = route.params.actionId
const args = { const args = {
"bindingId": actionId bindingId: actionId
} }
const response = await window.client.getActionBinding(args) const response = await window.client.getActionBinding(args)
action.value = response.action action.value = response.action

View File

@ -1,12 +1,21 @@
<template> <template>
<Section :title="'Execution conditions: ' + actionTitle" :padding="false"> <Section
:title="'Execution conditions: ' + actionTitle"
:padding="false"
>
<template #toolbar> <template #toolbar>
<router-link :to="{ name: 'ActionDetails', params: { actionId: route.params.actionId } }" class="button neutral"> <router-link
:to="{ name: 'ActionDetails', params: { actionId: route.params.actionId } }"
class="button neutral"
>
Back to action details Back to action details
</router-link> </router-link>
</template> </template>
<div v-if="action" class="padding content"> <div
v-if="action"
class="padding content"
>
<p> <p>
These entries mirror the automatic triggers from your OliveTin configuration for this action. These entries mirror the automatic triggers from your OliveTin configuration for this action.
You can always run the action manually as well. You can always run the action manually as well.
@ -14,7 +23,12 @@
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
On click On click
<a class="doc-link" :href="execConditionDocs.onDemand" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.onDemand"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<p> <p>
Manual execution from the web UI (dashboard or action details), or via the API (for example StartAction), Manual execution from the web UI (dashboard or action details), or via the API (for example StartAction),
@ -24,7 +38,12 @@
<template v-if="action.execOnStartup"> <template v-if="action.execOnStartup">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnStartup</code> <code>execOnStartup</code>
<a class="doc-link" :href="execConditionDocs.startup" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.startup"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<p>Runs once when OliveTin starts.</p> <p>Runs once when OliveTin starts.</p>
</template> </template>
@ -32,37 +51,72 @@
<template v-if="nonEmptyList(action.execOnCron)"> <template v-if="nonEmptyList(action.execOnCron)">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnCron</code> <code>execOnCron</code>
<a class="doc-link" :href="execConditionDocs.cron" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.cron"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<ul> <ul>
<li v-for="(line, idx) in action.execOnCron" :key="'cron-' + idx"><code>{{ line }}</code></li> <li
v-for="(line, idx) in action.execOnCron"
:key="'cron-' + idx"
>
<code>{{ line }}</code>
</li>
</ul> </ul>
</template> </template>
<template v-if="nonEmptyList(action.execOnFileCreatedInDir)"> <template v-if="nonEmptyList(action.execOnFileCreatedInDir)">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnFileCreatedInDir</code> <code>execOnFileCreatedInDir</code>
<a class="doc-link" :href="execConditionDocs.fileCreated" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.fileCreated"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<ul> <ul>
<li v-for="(dir, idx) in action.execOnFileCreatedInDir" :key="'created-' + idx"><code>{{ dir }}</code></li> <li
v-for="(dir, idx) in action.execOnFileCreatedInDir"
:key="'created-' + idx"
>
<code>{{ dir }}</code>
</li>
</ul> </ul>
</template> </template>
<template v-if="nonEmptyList(action.execOnFileChangedInDir)"> <template v-if="nonEmptyList(action.execOnFileChangedInDir)">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnFileChangedInDir</code> <code>execOnFileChangedInDir</code>
<a class="doc-link" :href="execConditionDocs.fileChanged" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.fileChanged"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<ul> <ul>
<li v-for="(dir, idx) in action.execOnFileChangedInDir" :key="'changed-' + idx"><code>{{ dir }}</code></li> <li
v-for="(dir, idx) in action.execOnFileChangedInDir"
:key="'changed-' + idx"
>
<code>{{ dir }}</code>
</li>
</ul> </ul>
</template> </template>
<template v-if="action.execOnCalendarFile"> <template v-if="action.execOnCalendarFile">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnCalendarFile</code> <code>execOnCalendarFile</code>
<a class="doc-link" :href="execConditionDocs.calendar" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.calendar"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<p><code>{{ action.execOnCalendarFile }}</code></p> <p><code>{{ action.execOnCalendarFile }}</code></p>
</template> </template>
@ -70,10 +124,18 @@
<template v-if="nonEmptyList(action.execOnWebhooks)"> <template v-if="nonEmptyList(action.execOnWebhooks)">
<h3 class="exec-type-heading"> <h3 class="exec-type-heading">
<code>execOnWebhook</code> <code>execOnWebhook</code>
<a class="doc-link" :href="execConditionDocs.webhook" target="_blank" rel="noopener noreferrer">Documentation</a> <a
class="doc-link"
:href="execConditionDocs.webhook"
target="_blank"
rel="noopener noreferrer"
>Documentation</a>
</h3> </h3>
<ul class="webhook-list"> <ul class="webhook-list">
<li v-for="(wh, idx) in action.execOnWebhooks" :key="'wh-' + idx"> <li
v-for="(wh, idx) in action.execOnWebhooks"
:key="'wh-' + idx"
>
<span v-if="wh.template">template: <code>{{ wh.template }}</code></span> <span v-if="wh.template">template: <code>{{ wh.template }}</code></span>
<span v-if="wh.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span> <span v-if="wh.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span>
<span v-if="nonEmptyObject(wh.matchHeaders)"> · matchHeaders: <code>{{ wh.matchHeaders }}</code></span> <span v-if="nonEmptyObject(wh.matchHeaders)"> · matchHeaders: <code>{{ wh.matchHeaders }}</code></span>
@ -83,14 +145,22 @@
</ul> </ul>
</template> </template>
<p v-if="!hasConfiguredTriggers" class="muted"> <p
v-if="!hasConfiguredTriggers"
class="muted"
>
This action has no automatic triggers in configuration besides on-demand execution. This action has no automatic triggers in configuration besides on-demand execution.
</p> </p>
</div> </div>
<div v-else-if="!loading" class="padding empty-state"> <div
v-else-if="!loading"
class="padding empty-state"
>
<p>Could not load this action.</p> <p>Could not load this action.</p>
<router-link :to="{ name: 'Actions' }">Return to index</router-link> <router-link :to="{ name: 'Actions' }">
Return to index
</router-link>
</div> </div>
</Section> </Section>
</template> </template>
@ -112,7 +182,7 @@ const execConditionDocs = {
fileCreated: 'https://docs.olivetin.app/action_execution/onfilecreated.html', fileCreated: 'https://docs.olivetin.app/action_execution/onfilecreated.html',
fileChanged: 'https://docs.olivetin.app/action_execution/onfilechanged.html', fileChanged: 'https://docs.olivetin.app/action_execution/onfilechanged.html',
calendar: 'https://docs.olivetin.app/action_execution/oncalendar.html', calendar: 'https://docs.olivetin.app/action_execution/oncalendar.html',
webhook: 'https://docs.olivetin.app/action_execution/onwebhook.html', webhook: 'https://docs.olivetin.app/action_execution/onwebhook.html'
} }
function nonEmptyList (list) { function nonEmptyList (list) {

View File

@ -8,7 +8,11 @@
:to="`/action/${bindingId}`" :to="`/action/${bindingId}`"
class="action-details-title-link" class="action-details-title-link"
> >
<ActionIconGlyph v-if="icon" class="action-title-icon" :glyph="icon" /> <ActionIconGlyph
v-if="icon"
class="action-title-icon"
:glyph="icon"
/>
{{ title }} {{ title }}
</router-link> </router-link>
</span> </span>
@ -17,48 +21,96 @@
<div class="section-content padding"> <div class="section-content padding">
<form @submit="handleSubmit"> <form @submit="handleSubmit">
<template v-if="actionArguments.length > 0"> <template v-if="actionArguments.length > 0">
<template
<template v-for="arg in actionArguments" :key="arg.name"> v-for="arg in actionArguments"
<label v-if="arg.type !== 'checklist'" :for="argumentFieldId(arg.name)"> :key="arg.name"
>
<label
v-if="arg.type !== 'checklist'"
:for="argumentFieldId(arg.name)"
>
{{ formatLabel(arg.title) }} {{ formatLabel(arg.title) }}
</label> </label>
<div v-else class="argument-label"> <div
v-else
class="argument-label"
>
{{ formatLabel(arg.title) }} {{ formatLabel(arg.title) }}
</div> </div>
<datalist v-if="(arg.suggestions && Object.keys(arg.suggestions).length > 0) || getBrowserSuggestions(arg).length > 0" :id="argumentFieldChoicesId(arg.name)"> <datalist
<option v-for="(suggestion, key) in arg.suggestions" :key="key" :value="key"> v-if="(arg.suggestions && Object.keys(arg.suggestions).length > 0) || getBrowserSuggestions(arg).length > 0"
:id="argumentFieldChoicesId(arg.name)"
>
<option
v-for="(suggestion, key) in arg.suggestions"
:key="key"
:value="key"
>
{{ suggestion }} {{ suggestion }}
</option> </option>
<option v-for="(suggestion, index) in getBrowserSuggestions(arg)" :key="`browser-${index}`" :value="suggestion"> <option
v-for="(suggestion, index) in getBrowserSuggestions(arg)"
:key="`browser-${index}`"
:value="suggestion"
>
{{ suggestion }} {{ suggestion }}
</option> </option>
</datalist> </datalist>
<ChoiceCombobox v-if="getInputComponent(arg) === 'select'" :id="argumentFieldId(arg.name)" :name="arg.name" <ChoiceCombobox
:choices="arg.choices" :model-value="getArgumentValue(arg)" :required="arg.required" v-if="getInputComponent(arg) === 'select'"
@update:model-value="handleChoiceUpdate(arg, $event)" /> :id="argumentFieldId(arg.name)"
:name="arg.name"
:choices="arg.choices"
:model-value="getArgumentValue(arg)"
:required="arg.required"
@update:model-value="handleChoiceUpdate(arg, $event)"
/>
<ChoiceChecklist v-else-if="arg.type === 'checklist'" :name="arg.name" <ChoiceChecklist
:label="arg.title" :choices="arg.choices" :model-value="getArgumentValue(arg)" :required="arg.required" v-else-if="arg.type === 'checklist'"
@update:model-value="handleChoiceUpdate(arg, $event)" /> :name="arg.name"
:label="arg.title"
:choices="arg.choices"
:model-value="getArgumentValue(arg)"
:required="arg.required"
@update:model-value="handleChoiceUpdate(arg, $event)"
/>
<component v-else :is="getInputComponent(arg)" :id="argumentFieldId(arg.name)" :name="arg.name" <component
:is="getInputComponent(arg)"
v-else
:id="argumentFieldId(arg.name)"
:name="arg.name"
:value="(arg.type === 'checkbox' || arg.type === 'confirmation') ? undefined : getArgumentValue(arg)" :value="(arg.type === 'checkbox' || arg.type === 'confirmation') ? undefined : getArgumentValue(arg)"
:checked="(arg.type === 'checkbox' || arg.type === 'confirmation') ? getArgumentValue(arg) : undefined" :checked="(arg.type === 'checkbox' || arg.type === 'confirmation') ? getArgumentValue(arg) : undefined"
:list="(arg.suggestions || getBrowserSuggestions(arg).length > 0) ? argumentFieldChoicesId(arg.name) : undefined" :list="(arg.suggestions || getBrowserSuggestions(arg).length > 0) ? argumentFieldChoicesId(arg.name) : undefined"
:type="getInputComponent(arg) !== 'select' ? getInputType(arg) : undefined" :type="getInputComponent(arg) !== 'select' ? getInputType(arg) : undefined"
:rows="arg.type === 'raw_string_multiline' ? 5 : undefined" :rows="arg.type === 'raw_string_multiline' ? 5 : undefined"
:step="arg.type === 'datetime' ? 1 : undefined" :pattern="getPattern(arg)" :step="arg.type === 'datetime' ? 1 : undefined"
@input="handleInput(arg, $event)" @change="handleChange(arg, $event)" /> :pattern="getPattern(arg)"
@input="handleInput(arg, $event)"
@change="handleChange(arg, $event)"
/>
<span class="argument-description" v-html="arg.description"></span> <span
class="argument-description"
v-html="arg.description"
/>
</template> </template>
</template> </template>
<template v-if="justificationRequired"> <template v-if="justificationRequired">
<label for="justification">Justification:</label> <label for="justification">Justification:</label>
<input id="justification" name="justification" type="text" :value="justificationValue" required @input="handleJustificationInput" /> <input
id="justification"
name="justification"
type="text"
:value="justificationValue"
required
@input="handleJustificationInput"
>
</template> </template>
<div v-if="actionArguments.length === 0 && !justificationRequired"> <div v-if="actionArguments.length === 0 && !justificationRequired">
@ -66,10 +118,18 @@
</div> </div>
<div class="buttons"> <div class="buttons">
<button name="start" type="submit" :disabled="!formReady || (hasConfirmation && !confirmationChecked)"> <button
name="start"
type="submit"
:disabled="!formReady || (hasConfirmation && !confirmationChecked)"
>
Start Start
</button> </button>
<button name="cancel" type="button" @click="handleCancel"> <button
name="cancel"
type="button"
@click="handleCancel"
>
Cancel Cancel
</button> </button>
</div> </div>
@ -316,7 +376,7 @@ async function validateArgument(arg, value) {
try { try {
const validateArgumentTypeArgs = { const validateArgumentTypeArgs = {
value: value, value,
type: arg.type, type: arg.type,
bindingId: props.bindingId, bindingId: props.bindingId,
argumentName: arg.name argumentName: arg.name
@ -638,7 +698,6 @@ form {
grid-template-columns: max-content auto auto; grid-template-columns: max-content auto auto;
} }
.argument-description { .argument-description {
font-size: 0.875rem; font-size: 0.875rem;
color: #666; color: #666;

View File

@ -1,10 +1,14 @@
<template> <template>
<Section :title="t('diagnostics.get-support')"> <Section :title="t('diagnostics.get-support')">
<p>{{ t('diagnostics.get-support-description') }} <p>
{{ t('diagnostics.get-support-description') }}
</p> </p>
<ul> <ul>
<li> <li>
<a href = "https://docs.olivetin.app/troubleshooting/wheretofindhelp.html" target="_blank">{{ t('diagnostics.where-to-find-help') }}</a> <a
href="https://docs.olivetin.app/troubleshooting/wheretofindhelp.html"
target="_blank"
>{{ t('diagnostics.where-to-find-help') }}</a>
</li> </li>
</ul> </ul>
</Section> </Section>
@ -21,26 +25,61 @@
<Section :title="t('diagnostics.server-diagnostics')"> <Section :title="t('diagnostics.server-diagnostics')">
<p>{{ t('diagnostics.server-diagnostics-description') }}</p> <p>{{ t('diagnostics.server-diagnostics-description') }}</p>
<p> <p>
<a href="https://docs.olivetin.app/troubleshooting/server-diagnostics.html" target="_blank">{{ t('diagnostics.server-diagnostics-docs') }}</a> <a
href="https://docs.olivetin.app/troubleshooting/server-diagnostics.html"
target="_blank"
>{{ t('diagnostics.server-diagnostics-docs') }}</a>
</p> </p>
<div role="toolbar"> <div role="toolbar">
<button @click="generateServerDiagnostics" :disabled="loading" class = "good">{{ t('diagnostics.generate-server-diagnostics') }}</button> <button
<button @click="copyServerDiagnostics" :disabled="!serverDiagnostics || loading" :class="serverDiagnosticsCopied ? 'good' : ''">{{ serverDiagnosticsCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}</button> :disabled="loading"
class="good"
@click="generateServerDiagnostics"
>
{{ t('diagnostics.generate-server-diagnostics') }}
</button>
<button
:disabled="!serverDiagnostics || loading"
:class="serverDiagnosticsCopied ? 'good' : ''"
@click="copyServerDiagnostics"
>
{{ serverDiagnosticsCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}
</button>
</div> </div>
<textarea v-model="serverDiagnostics" readonly style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"></textarea> <textarea
v-model="serverDiagnostics"
readonly
style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"
/>
</Section> </Section>
<Section :title="t('diagnostics.browser-info')"> <Section :title="t('diagnostics.browser-info')">
<p>{{ t('diagnostics.browser-info-description') }}</p> <p>{{ t('diagnostics.browser-info-description') }}</p>
<div role="toolbar"> <div role="toolbar">
<button @click="generateBrowserInfo" :disabled="loading" class = "good">{{ t('diagnostics.generate-browser-info') }}</button> <button
<button @click="copyBrowserInfo" :disabled="!browserInfo || loading" :class="browserInfoCopied ? 'good' : ''">{{ browserInfoCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}</button> :disabled="loading"
class="good"
@click="generateBrowserInfo"
>
{{ t('diagnostics.generate-browser-info') }}
</button>
<button
:disabled="!browserInfo || loading"
:class="browserInfoCopied ? 'good' : ''"
@click="copyBrowserInfo"
>
{{ browserInfoCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}
</button>
</div> </div>
<textarea v-model="browserInfo" readonly style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"></textarea> <textarea
v-model="browserInfo"
readonly
style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"
/>
</Section> </Section>
</template> </template>
@ -62,13 +101,13 @@ async function fetchDiagnostics() {
loading.value = true loading.value = true
try { try {
const response = await window.client.getDiagnostics(); const response = await window.client.getDiagnostics()
diagnostics.value = { diagnostics.value = {
sshFoundKey: response.SshFoundKey, sshFoundKey: response.SshFoundKey,
sshFoundConfig: response.SshFoundConfig sshFoundConfig: response.SshFoundConfig
}; }
} catch (err) { } catch (err) {
console.error('Failed to fetch diagnostics:', err); console.error('Failed to fetch diagnostics:', err)
diagnostics.value = { diagnostics.value = {
sshFoundKey: t('diagnostics.unknown'), sshFoundKey: t('diagnostics.unknown'),
sshFoundConfig: t('diagnostics.unknown') sshFoundConfig: t('diagnostics.unknown')
@ -77,16 +116,9 @@ async function fetchDiagnostics() {
loading.value = false loading.value = false
} }
function formatKey(key) {
return key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase())
.trim()
}
async function generateServerDiagnostics () { async function generateServerDiagnostics () {
const response = await window.client.serverDiagnostics() const response = await window.client.serverDiagnostics()
console.log("response", response) console.log('response', response)
serverDiagnostics.value = `\`\`\`\n${response.alert}\n\`\`\`\n` serverDiagnostics.value = `\`\`\`\n${response.alert}\n\`\`\`\n`
} }
@ -159,7 +191,7 @@ async function generateBrowserInfo() {
})(), })(),
hardwareConcurrency: navigator.hardwareConcurrency || 'N/A', hardwareConcurrency: navigator.hardwareConcurrency || 'N/A',
maxTouchPoints: navigator.maxTouchPoints || 'N/A', maxTouchPoints: navigator.maxTouchPoints || 'N/A',
userAgentData: userAgentData userAgentData
} }
const showVersionNumber = window.initResponse?.effectivePolicy?.showVersionNumber ?? true const showVersionNumber = window.initResponse?.effectivePolicy?.showVersionNumber ?? true
@ -168,20 +200,20 @@ async function generateBrowserInfo() {
: '[hidden]' : '[hidden]'
const currentLanguage = locale.value || t('diagnostics.unknown') const currentLanguage = locale.value || t('diagnostics.unknown')
let output = ''; let output = ''
output += `\`\`\`\n` output += '```\n'
output += '### BROWSER INFO START (copy all text to BROWSER INFO END)\n' output += '### BROWSER INFO START (copy all text to BROWSER INFO END)\n'
output += `# OliveTin Information\n` output += '# OliveTin Information\n'
output += `olivetinVersion: ${olivetinVersion}\n` output += `olivetinVersion: ${olivetinVersion}\n`
output += `currentLanguage: ${currentLanguage}\n` output += `currentLanguage: ${currentLanguage}\n`
output += `\n# Browser Information\n` output += '\n# Browser Information\n'
output += `userAgent: ${info.userAgent}\n` output += `userAgent: ${info.userAgent}\n`
output += `platform: ${info.platform}\n` output += `platform: ${info.platform}\n`
output += `language: ${info.language}\n` output += `language: ${info.language}\n`
output += `languages: ${info.languages}\n` output += `languages: ${info.languages}\n`
output += `\n# User Agent Data\n` output += '\n# User Agent Data\n'
output += `userAgentData:\n${info.userAgentData}\n` output += `userAgentData:\n${info.userAgentData}\n`
output += `\n# Display Information\n` output += '\n# Display Information\n'
output += `screenWidth: ${info.screenWidth}\n` output += `screenWidth: ${info.screenWidth}\n`
output += `screenHeight: ${info.screenHeight}\n` output += `screenHeight: ${info.screenHeight}\n`
output += `screenColorDepth: ${info.screenColorDepth}\n` output += `screenColorDepth: ${info.screenColorDepth}\n`
@ -189,18 +221,18 @@ async function generateBrowserInfo() {
output += `viewportWidth: ${info.viewportWidth}\n` output += `viewportWidth: ${info.viewportWidth}\n`
output += `viewportHeight: ${info.viewportHeight}\n` output += `viewportHeight: ${info.viewportHeight}\n`
output += `devicePixelRatio: ${info.devicePixelRatio}\n` output += `devicePixelRatio: ${info.devicePixelRatio}\n`
output += `\n# Feature Support\n` output += '\n# Feature Support\n'
output += `cookieEnabled: ${info.cookieEnabled}\n` output += `cookieEnabled: ${info.cookieEnabled}\n`
output += `localStorageEnabled: ${info.localStorageEnabled}\n` output += `localStorageEnabled: ${info.localStorageEnabled}\n`
output += `sessionStorageEnabled: ${info.sessionStorageEnabled}\n` output += `sessionStorageEnabled: ${info.sessionStorageEnabled}\n`
output += `onLine: ${info.onLine}\n` output += `onLine: ${info.onLine}\n`
output += `hardwareConcurrency: ${info.hardwareConcurrency}\n` output += `hardwareConcurrency: ${info.hardwareConcurrency}\n`
output += `maxTouchPoints: ${info.maxTouchPoints}\n` output += `maxTouchPoints: ${info.maxTouchPoints}\n`
output += `\n# Location & Time\n` output += '\n# Location & Time\n'
output += `timezone: ${info.timezone}\n` output += `timezone: ${info.timezone}\n`
output += `timezoneOffset: ${info.timezoneOffset}\n` output += `timezoneOffset: ${info.timezoneOffset}\n`
output += `\n### BROWSER INFO END (copy all text from BROWSER INFO START)` output += '\n### BROWSER INFO END (copy all text from BROWSER INFO START)'
output += `\n\`\`\`\n` output += '\n```\n'
browserInfo.value = output browserInfo.value = output
} finally { } finally {

View File

@ -1,5 +1,8 @@
<template> <template>
<Section v-if="!definitionsLoaded" title="Loading entity definitions..." /> <Section
v-if="!definitionsLoaded"
title="Loading entity definitions..."
/>
<Section <Section
v-else-if="totalInstances === 0" v-else-if="totalInstances === 0"
title="There are no entities to show yet." title="There are no entities to show yet."
@ -28,8 +31,8 @@
const totalInstances = computed(() => const totalInstances = computed(() =>
entityDefinitions.value.reduce( entityDefinitions.value.reduce(
(sum, def) => sum + (def.instances?.length ?? 0), (sum, def) => sum + (def.instances?.length ?? 0),
0, 0
), )
) )
async function fetchEntities () { async function fetchEntities () {

View File

@ -3,13 +3,24 @@
<template #title> <template #title>
<span class="section-title-with-icon"> <span class="section-title-with-icon">
Entity Details: Entity Details:
<ActionIconGlyph v-if="entityIcon" class="entity-title-icon" :glyph="entityIcon" /> <ActionIconGlyph
v-if="entityIcon"
class="entity-title-icon"
:glyph="entityIcon"
/>
<span v-if="entityDetails?.title">{{ entityDetails.title }}</span> <span v-if="entityDetails?.title">{{ entityDetails.title }}</span>
</span> </span>
</template> </template>
<template #toolbar> <template #toolbar>
<button @click="goBack" class="back-button"> <button
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" /> class="back-button"
@click="goBack"
>
<HugeiconsIcon
:icon="ArrowLeftIcon"
width="1.2em"
height="1.2em"
/>
<span>Back</span> <span>Back</span>
</button> </button>
</template> </template>
@ -20,27 +31,48 @@
<dl> <dl>
<dt>Type</dt> <dt>Type</dt>
<dd> <dd>
<router-link :to="{ name: 'Entities' }" class="entity-type-link"> <router-link
:to="{ name: 'Entities' }"
class="entity-type-link"
>
{{ entityType }} {{ entityType }}
</router-link> </router-link>
</dd> </dd>
<dt v-if="entityDetails.title">Title</dt> <dt v-if="entityDetails.title">
<dd v-if="entityDetails.title">{{ entityDetails.title }}</dd> Title
</dt>
<dd v-if="entityDetails.title">
{{ entityDetails.title }}
</dd>
<template v-if="entityDetails.fields"> <template v-if="entityDetails.fields">
<template v-for="(value, key) in entityDetails.fields" :key="key"> <template
v-for="(value, key) in entityDetails.fields"
:key="key"
>
<dt>{{ key }}</dt> <dt>{{ key }}</dt>
<dd>{{ value }}</dd> <dd>{{ value }}</dd>
</template> </template>
</template> </template>
</dl> </dl>
<p v-if="!entityDetails.title && (!entityDetails.fields || Object.keys(entityDetails.fields).length === 0)">No details available for this entity.</p> <p v-if="!entityDetails.title && (!entityDetails.fields || Object.keys(entityDetails.fields).length === 0)">
No details available for this entity.
</p>
</template> </template>
</Section> </Section>
<Section v-if="entityDetails" title="Dashboard Entity Directories"> <Section
<div v-if="filteredDirectories.length > 0" class="directories-section"> v-if="entityDetails"
title="Dashboard Entity Directories"
>
<div
v-if="filteredDirectories.length > 0"
class="directories-section"
>
<ul class="directory-list"> <ul class="directory-list">
<li v-for="(directory, idx) in filteredDirectories" :key="idx"> <li
v-for="(directory, idx) in filteredDirectories"
:key="idx"
>
<router-link <router-link
:to="{ :to="{
name: 'Dashboard', name: 'Dashboard',
@ -49,22 +81,36 @@
entityType: entityType, entityType: entityType,
entityKey: entityKey entityKey: entityKey
} }
}"> }"
>
{{ directory }} {{ directory }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
</div> </div>
<p v-else>No directories found for this entity. <p v-else>
<a href="https://docs.olivetin.app/dashboards/entity-directories.html" target="_blank" rel="noopener noreferrer">Learn more</a> No directories found for this entity.
<a
href="https://docs.olivetin.app/dashboards/entity-directories.html"
target="_blank"
rel="noopener noreferrer"
>Learn more</a>
</p> </p>
</Section> </Section>
<section v-if="entityDetails && relatedActions.length > 0" class="transparent"> <section
v-if="entityDetails && relatedActions.length > 0"
class="transparent"
>
<div class="dashboard-row"> <div class="dashboard-row">
<fieldset> <fieldset>
<legend class="visually-hidden">Related actions</legend> <legend class="visually-hidden">
<template v-for="(related, idx) in relatedActions" :key="related.action?.bindingId || idx"> Related actions
</legend>
<template
v-for="(related, idx) in relatedActions"
:key="related.action?.bindingId || idx"
>
<ActionButton <ActionButton
v-if="related.action" v-if="related.action"
:action-data="related.action" :action-data="related.action"

View File

@ -9,12 +9,23 @@
class="action-details-title-link" class="action-details-title-link"
:title="titleTooltip" :title="titleTooltip"
> >
<ActionIconGlyph class="action-title-icon" :glyph="icon" /> <ActionIconGlyph
<LogActionTitle v-if="logEntry" :action-title="title" :justification="logEntry.justification" /> class="action-title-icon"
:glyph="icon"
/>
<LogActionTitle
v-if="logEntry"
:action-title="title"
:justification="logEntry.justification"
/>
<span v-else>{{ title }}</span> <span v-else>{{ title }}</span>
</router-link> </router-link>
<template v-else> <template v-else>
<LogActionTitle v-if="logEntry" :action-title="title" :justification="logEntry.justification" /> <LogActionTitle
v-if="logEntry"
:action-title="title"
:justification="logEntry.justification"
/>
<span v-else>{{ title }}</span> <span v-else>{{ title }}</span>
</template> </template>
</span> </span>
@ -23,68 +34,111 @@
<button <button
v-for="dashboard in backToDashboards" v-for="dashboard in backToDashboards"
:key="dashboard.path" :key="dashboard.path"
@click="goToDashboard(dashboard.path)"
:title="'Back to ' + dashboard.title" :title="'Back to ' + dashboard.title"
class="button neutral" class="button neutral"
@click="goToDashboard(dashboard.path)"
> >
<HugeiconsIcon :icon="DashboardSquare01Icon" /> <HugeiconsIcon :icon="DashboardSquare01Icon" />
{{ dashboard.title }} {{ dashboard.title }}
</button> </button>
<button v-if="backToDashboards.length === 0" @click="goBack" title="Go back" class="button neutral"> <button
v-if="backToDashboards.length === 0"
title="Go back"
class="button neutral"
@click="goBack"
>
<HugeiconsIcon :icon="ArrowLeftIcon" /> <HugeiconsIcon :icon="ArrowLeftIcon" />
Back Back
</button> </button>
</template> </template>
<div v-if="logEntry" class = "flex-row"> <div
v-if="logEntry"
class="flex-row"
>
<dl class="fg1"> <dl class="fg1">
<dt>Duration</dt> <dt>Duration</dt>
<dd><span v-html="duration"></span></dd> <dd><span v-html="duration" /></dd>
<dt>Status</dt> <dt>Status</dt>
<dd class="execution-dialog-status"> <dd class="execution-dialog-status">
<ActionStatusDisplay :log-entry="logEntry" :link-queued-status="true" /> <ActionStatusDisplay
:log-entry="logEntry"
:link-queued-status="true"
/>
</dd> </dd>
</dl> </dl>
</div> </div>
<div v-if="notFound" class="error-message padded-content"> <div
v-if="notFound"
class="error-message padded-content"
>
<h3>Execution Not Found</h3> <h3>Execution Not Found</h3>
<p>{{ errorMessage }}</p> <p>{{ errorMessage }}</p>
<p>The execution with ID <code>{{ executionTrackingId }}</code> could not be found.</p> <p>The execution with ID <code>{{ executionTrackingId }}</code> could not be found.</p>
<router-link to="/logs">View all logs</router-link> or <router-link to="/">return to home</router-link>. <router-link to="/logs">
View all logs
</router-link> or <router-link to="/">
return to home
</router-link>.
</div> </div>
<div class="xterm-output-container"> <div class="xterm-output-container">
<div class="xterm-overlay-toolbar"> <div class="xterm-overlay-toolbar">
<button type="button" class="xterm-overlay-button" @click="copyOutput" title="Copy to clipboard"> <button
type="button"
class="xterm-overlay-button"
title="Copy to clipboard"
@click="copyOutput"
>
<HugeiconsIcon :icon="Copy01Icon" /> <HugeiconsIcon :icon="Copy01Icon" />
</button> </button>
<button type="button" class="xterm-overlay-button" @click="toggleSize" title="Toggle fullscreen"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> type="button"
<path fill="currentColor" class="xterm-overlay-button"
d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z" /> title="Toggle fullscreen"
@click="toggleSize"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z"
/>
</svg> </svg>
</button> </button>
</div> </div>
<div ref="xtermOutput"></div> <div ref="xtermOutput" />
</div> </div>
<br /> <br>
<div class="flex-row g1 buttons padded-content"> <div class="flex-row g1 buttons padded-content">
<div class="fg1" /> <div class="fg1" />
<button :disabled="!canRerun" @click="rerunAction" title="Rerun"> <button
:disabled="!canRerun"
title="Rerun"
@click="rerunAction"
>
<HugeiconsIcon :icon="WorkoutRunIcon" /> <HugeiconsIcon :icon="WorkoutRunIcon" />
Rerun Rerun
</button> </button>
<button :disabled="!canKill" @click="killAction" title="Kill" id = "execution-dialog-kill-action"> <button
id="execution-dialog-kill-action"
:disabled="!canKill"
title="Kill"
@click="killAction"
>
<HugeiconsIcon :icon="Cancel02Icon" /> <HugeiconsIcon :icon="Cancel02Icon" />
Kill Kill
</button> </button>
</div> </div>
</Section> </Section>
</template> </template>
@ -276,7 +330,7 @@ function hideEverythingApartFromOutput() {
} }
async function fetchExecutionResult (executionTrackingIdParam) { async function fetchExecutionResult (executionTrackingIdParam) {
console.log("fetchExecutionResult", executionTrackingIdParam) console.log('fetchExecutionResult', executionTrackingIdParam)
executionTrackingId.value = executionTrackingIdParam executionTrackingId.value = executionTrackingIdParam
notFound.value = false notFound.value = false
@ -307,7 +361,6 @@ function updateDuration(logEntryParam) {
logEntry.value = logEntryParam logEntry.value = logEntryParam
if (logEntry.value == null) { if (logEntry.value == null) {
duration.value = executionSeconds.value + ' seconds' duration.value = executionSeconds.value + ' seconds'
duration.value = duration.value
} else if (!logEntry.value.executionStarted) { } else if (!logEntry.value.executionStarted) {
duration.value = logEntry.value.datetimeStarted + ' (request time). Not executed.' duration.value = logEntry.value.datetimeStarted + ' (request time). Not executed.'
} else if (logEntry.value.executionStarted && !logEntry.value.executionFinished) { } else if (logEntry.value.executionStarted && !logEntry.value.executionFinished) {
@ -418,7 +471,6 @@ onMounted(() => {
} }
} }
) )
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -1,33 +1,78 @@
<template> <template>
<Section title="Login to OliveTin" class="small"> <Section
title="Login to OliveTin"
class="small"
>
<div class="login-form"> <div class="login-form">
<div v-if="!hasOAuth && !hasLocalLogin" class="login-disabled"> <div
v-if="!hasOAuth && !hasLocalLogin"
class="login-disabled"
>
<span>This server is not configured with either OAuth, or local users, so you cannot login.</span> <span>This server is not configured with either OAuth, or local users, so you cannot login.</span>
</div> </div>
<div v-if="hasOAuth" class="login-oauth2"> <div
v-if="hasOAuth"
class="login-oauth2"
>
<h3>OAuth Login</h3> <h3>OAuth Login</h3>
<div class="oauth-providers"> <div class="oauth-providers">
<button v-for="provider in oauthProviders" :key="provider.key" class="oauth-button" <button
@click="loginWithOAuth(provider)"> v-for="provider in oauthProviders"
<span v-if="provider.icon" class="provider-icon" v-html="provider.icon"></span> :key="provider.key"
class="oauth-button"
@click="loginWithOAuth(provider)"
>
<span
v-if="provider.icon"
class="provider-icon"
v-html="provider.icon"
/>
<span class="provider-name">Login with {{ provider.title }}</span> <span class="provider-name">Login with {{ provider.title }}</span>
</button> </button>
</div> </div>
</div> </div>
<div v-if="hasLocalLogin" class="login-local"> <div
v-if="hasLocalLogin"
class="login-local"
>
<h3>Local Login</h3> <h3>Local Login</h3>
<form @submit.prevent="handleLocalLogin" class="local-login-form"> <form
<div v-if="loginError" class="bad"> class="local-login-form"
@submit.prevent="handleLocalLogin"
>
<div
v-if="loginError"
class="bad"
>
{{ loginError }} {{ loginError }}
</div> </div>
<input id="username" v-model="username" type="text" name="username" autocomplete="username" required placeholder="Username" /> <input
<input id="password" v-model="password" type="password" name="password" autocomplete="current-password" placeholder="Password" id="username"
required /> v-model="username"
type="text"
name="username"
autocomplete="username"
required
placeholder="Username"
>
<input
id="password"
v-model="password"
type="password"
name="password"
autocomplete="current-password"
placeholder="Password"
required
>
<button type="submit" :disabled="loading" class="login-button"> <button
type="submit"
:disabled="loading"
class="login-button"
>
{{ loading ? 'Logging in...' : 'Login' }} {{ loading ? 'Logging in...' : 'Login' }}
</button> </button>
</form> </form>
@ -119,7 +164,7 @@ onMounted(() => {
loadLoginOptions() loadLoginOptions()
// Also watch for when init response becomes available // Also watch for when init response becomes available
const stopWatcher = watch(() => window.initResponse, () => { watch(() => window.initResponse, () => {
loadLoginOptions() loadLoginOptions()
}, { immediate: true }) }, { immediate: true })
}) })

View File

@ -1,9 +1,23 @@
<template> <template>
<Section :title="t('logs.calendar-title')" :padding="false"> <Section
:title="t('logs.calendar-title')"
:padding="false"
>
<template #toolbar> <template #toolbar>
<router-link to="/logs" class="button neutral"> <router-link
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> to="/logs"
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z"/> class="button neutral"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z"
/>
</svg> </svg>
{{ t('logs.back-to-list') }} {{ t('logs.back-to-list') }}
</router-link> </router-link>
@ -62,8 +76,8 @@ const calendarEvents = computed(() => {
id: log.executionTrackingId, id: log.executionTrackingId,
title: log.actionTitle || 'Untitled Action', title: log.actionTitle || 'Untitled Action',
date: startDate, date: startDate,
startDate: startDate, startDate,
endDate: endDate, endDate,
actionIcon: log.actionIcon, actionIcon: log.actionIcon,
user: log.user, user: log.user,
tags: log.tags, tags: log.tags,
@ -82,11 +96,10 @@ async function fetchLogs() {
const allLogs = [] const allLogs = []
let startOffset = BigInt(0) let startOffset = BigInt(0)
const maxLogs = 10000 // Reasonable limit to prevent excessive API calls const maxLogs = 10000 // Reasonable limit to prevent excessive API calls
const pageSize = 100 // Typical page size, will be updated from API response
while (allLogs.length < maxLogs) { while (allLogs.length < maxLogs) {
const args = { const args = {
"startOffset": startOffset, startOffset
} }
const response = await window.client.getLogs(args) const response = await window.client.getLogs(args)

View File

@ -1,30 +1,61 @@
<template> <template>
<Section :title="t('logs.title')" :padding="false"> <Section
:title="t('logs.title')"
:padding="false"
>
<template #toolbar> <template #toolbar>
<router-link to="/logs/queue" class="button neutral"> <router-link
to="/logs/queue"
class="button neutral"
>
{{ t('logs.queue') }} {{ t('logs.queue') }}
</router-link> </router-link>
<router-link to="/logs/calendar" class="button neutral"> <router-link
to="/logs/calendar"
class="button neutral"
>
{{ t('logs.calendar') }} {{ t('logs.calendar') }}
</router-link> </router-link>
<label class="input-with-icons"> <label class="input-with-icons">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<path fill="currentColor" xmlns="http://www.w3.org/2000/svg"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" /> width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"
/>
</svg> </svg>
<input <input
:placeholder="t('logs.filter-placeholder')"
v-model="searchText" v-model="searchText"
:placeholder="t('logs.filter-placeholder')"
list="logs-filter-suggestions" list="logs-filter-suggestions"
:aria-invalid="filterError ? 'true' : 'false'" :aria-invalid="filterError ? 'true' : 'false'"
/> >
<datalist id="logs-filter-suggestions"> <datalist id="logs-filter-suggestions">
<option v-for="suggestion in filterSuggestions" :key="suggestion" :value="suggestion" /> <option
v-for="suggestion in filterSuggestions"
:key="suggestion"
:value="suggestion"
/>
</datalist> </datalist>
<button :title="t('logs.clear-filter')" :disabled="!searchText" @click="clearSearch"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> :title="t('logs.clear-filter')"
<path fill="currentColor" :disabled="!searchText"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" /> @click="clearSearch"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
/>
</svg> </svg>
</button> </button>
</label> </label>
@ -38,7 +69,13 @@
<p>{{ t('logs.filter-help-fields') }}</p> <p>{{ t('logs.filter-help-fields') }}</p>
<p><code>{{ t('logs.filter-help-examples') }}</code></p> <p><code>{{ t('logs.filter-help-examples') }}</code></p>
</details> </details>
<p v-if="filterError" class="filter-error" role="alert">{{ filterError }}</p> <p
v-if="filterError"
class="filter-error"
role="alert"
>
{{ filterError }}
</p>
</div> </div>
<div v-show="logs.length > 0"> <div v-show="logs.length > 0">
@ -48,12 +85,26 @@
<th> <th>
<div class="timestamp-header"> <div class="timestamp-header">
<span>{{ t('logs.timestamp') }}</span> <span>{{ t('logs.timestamp') }}</span>
<span v-if="selectedDate" class="date-filter-indicator"> <span
v-if="selectedDate"
class="date-filter-indicator"
>
<span class="date-filter-text">{{ formatDateFilter(selectedDate) }}</span> <span class="date-filter-text">{{ formatDateFilter(selectedDate) }}</span>
<button :title="t('logs.clear-date-filter')" @click="clearDateFilter" class="clear-date-button"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> :title="t('logs.clear-date-filter')"
<path fill="currentColor" class="clear-date-button"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" /> @click="clearDateFilter"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"
/>
</svg> </svg>
</button> </button>
</span> </span>
@ -65,12 +116,25 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="log in logs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle"> <tr
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td> v-for="log in logs"
:key="log.executionTrackingId"
class="log-row"
:title="log.actionTitle"
>
<td class="timestamp">
{{ formatTimestamp(log.datetimeStarted) }}
</td>
<td> <td>
<ActionIconGlyph class="icon" :glyph="log.actionIcon" /> <ActionIconGlyph
class="icon"
:glyph="log.actionIcon"
/>
<router-link :to="`/logs/${log.executionTrackingId}`"> <router-link :to="`/logs/${log.executionTrackingId}`">
<LogActionTitle :action-title="log.actionTitle" :justification="log.justification" /> <LogActionTitle
:action-title="log.actionTitle"
:justification="log.justification"
/>
</router-link> </router-link>
</td> </td>
<td class="tags"> <td class="tags">
@ -78,38 +142,70 @@
<span class="annotation-key">User:</span> <span class="annotation-key">User:</span>
<span class="annotation-val">{{ log.user }}</span> <span class="annotation-val">{{ log.user }}</span>
</span> </span>
<span v-if="log.tags && log.tags.length > 0" class="tag-list"> <span
<span v-for="tag in log.tags" :key="tag" class="tag">{{ tag }}</span> v-if="log.tags && log.tags.length > 0"
class="tag-list"
>
<span
v-for="tag in log.tags"
:key="tag"
class="tag"
>{{ tag }}</span>
</span> </span>
</td> </td>
<td class="exit-code"> <td class="exit-code">
<ActionStatusDisplay :logEntry="log" /> <ActionStatusDisplay :log-entry="log" />
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class = "padding" <Pagination
@page-size-change="handlePageSizeChange" itemTitle="execution logs" /> :page-size="pageSize"
:total="totalCount"
:current-page="currentPage"
:page="currentPage"
class="padding"
item-title="execution logs"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
</div> </div>
<div v-show="logs.length === 0 && !loading && searchText && !filterError" class="empty-state padding"> <div
v-show="logs.length === 0 && !loading && searchText && !filterError"
class="empty-state padding"
>
<p>{{ t('logs.no-logs-for-filter') }}</p> <p>{{ t('logs.no-logs-for-filter') }}</p>
<button @click="clearSearch" class="button neutral"> <button
class="button neutral"
@click="clearSearch"
>
{{ t('logs.clear-filter') }} {{ t('logs.clear-filter') }}
</button> </button>
</div> </div>
<div v-show="selectedDate && logs.length === 0 && !loading && !searchText" class="empty-state padding"> <div
v-show="selectedDate && logs.length === 0 && !loading && !searchText"
class="empty-state padding"
>
<p>{{ t('logs.no-logs-to-display') }} {{ formatDateFilter(selectedDate) }}.</p> <p>{{ t('logs.no-logs-to-display') }} {{ formatDateFilter(selectedDate) }}.</p>
<button @click="clearDateFilter" class="button neutral"> <button
class="button neutral"
@click="clearDateFilter"
>
{{ t('logs.clear-date-filter') }} {{ t('logs.clear-date-filter') }}
</button> </button>
</div> </div>
<div v-show="logs.length === 0 && !loading && !selectedDate && !searchText" class="empty-state padding"> <div
v-show="logs.length === 0 && !loading && !selectedDate && !searchText"
class="empty-state padding"
>
<p>{{ t('logs.no-logs-to-display') }}</p> <p>{{ t('logs.no-logs-to-display') }}</p>
<router-link to="/">{{ t('return-to-index') }}</router-link> <router-link to="/">
{{ t('return-to-index') }}
</router-link>
</div> </div>
</Section> </Section>
</template> </template>

View File

@ -1,19 +1,40 @@
<template> <template>
<Section :title="t('logs.queue-title')" :padding="false"> <Section
:title="t('logs.queue-title')"
:padding="false"
>
<template #toolbar> <template #toolbar>
<router-link to="/logs" class="button neutral"> <router-link
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> to="/logs"
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z"/> class="button neutral"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z"
/>
</svg> </svg>
{{ t('logs.back-to-list') }} {{ t('logs.back-to-list') }}
</router-link> </router-link>
</template> </template>
<p class="padding">{{ t('logs.queue-page-description') }}</p> <p class="padding">
{{ t('logs.queue-page-description') }}
</p>
<div v-if="groups.length === 0 && !loading" class="empty-state padding"> <div
v-if="groups.length === 0 && !loading"
class="empty-state padding"
>
<p>{{ t('logs.queue-empty') }}</p> <p>{{ t('logs.queue-empty') }}</p>
<router-link to="/logs">{{ t('logs.back-to-list') }}</router-link> <router-link to="/logs">
{{ t('logs.back-to-list') }}
</router-link>
</div> </div>
</Section> </Section>
@ -24,7 +45,10 @@
> >
<div class="section-header flex-row"> <div class="section-header flex-row">
<div class="fg1 queue-action-group-heading"> <div class="fg1 queue-action-group-heading">
<ActionIconGlyph class="icon" :glyph="actionGroup.icon" /> <ActionIconGlyph
class="icon"
:glyph="actionGroup.icon"
/>
<h2>{{ displayActionGroupName(actionGroup.name) }}</h2> <h2>{{ displayActionGroupName(actionGroup.name) }}</h2>
</div> </div>
<ActionGroupLimitsLabel <ActionGroupLimitsLabel
@ -44,23 +68,34 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<template v-for="action in actionGroup.actions" :key="`${actionGroup.name}:${action.bindingId}`"> <template
v-for="action in actionGroup.actions"
:key="`${actionGroup.name}:${action.bindingId}`"
>
<tr <tr
v-for="(entry, index) in action.entries" v-for="(entry, index) in action.entries"
:key="entry.executionTrackingId" :key="entry.executionTrackingId"
class="log-row" class="log-row"
:title="action.actionTitle" :title="action.actionTitle"
> >
<td class="timestamp">{{ formatTimestamp(entry.datetimeStarted) }}</td> <td class="timestamp">
{{ formatTimestamp(entry.datetimeStarted) }}
</td>
<td> <td>
<ActionIconGlyph class="icon" :glyph="action.actionIcon" /> <ActionIconGlyph
class="icon"
:glyph="action.actionIcon"
/>
<router-link :to="`/logs/${entry.executionTrackingId}`"> <router-link :to="`/logs/${entry.executionTrackingId}`">
<LogActionTitle <LogActionTitle
:action-title="action.actionTitle" :action-title="action.actionTitle"
:justification="entry.justification" :justification="entry.justification"
/> />
</router-link> </router-link>
<span v-if="action.entityPrefix" class="queue-entity annotation"> <span
v-if="action.entityPrefix"
class="queue-entity annotation"
>
{{ action.entityPrefix }} {{ action.entityPrefix }}
</span> </span>
</td> </td>
@ -69,15 +104,28 @@
<span class="annotation-key">User:</span> <span class="annotation-key">User:</span>
<span class="annotation-val">{{ entry.user }}</span> <span class="annotation-val">{{ entry.user }}</span>
</span> </span>
<span v-if="entry.tags && entry.tags.length > 0" class="tag-list"> <span
<span v-for="tag in entry.tags" :key="tag" class="tag">{{ tag }}</span> v-if="entry.tags && entry.tags.length > 0"
class="tag-list"
>
<span
v-for="tag in entry.tags"
:key="tag"
class="tag"
>{{ tag }}</span>
</span> </span>
</td> </td>
<td class="exit-code"> <td class="exit-code">
<span v-if="!entry.executionFinished" class="queue-position"> <span
v-if="!entry.executionFinished"
class="queue-position"
>
{{ t('logs.queue-position', { position: index + 1 }) }} {{ t('logs.queue-position', { position: index + 1 }) }}
</span> </span>
<ActionStatusDisplay :logEntry="entry" link-queued-status /> <ActionStatusDisplay
:log-entry="entry"
link-queued-status
/>
</td> </td>
</tr> </tr>
</template> </template>

View File

@ -6,10 +6,16 @@
<h2>Page Not Found</h2> <h2>Page Not Found</h2>
<div class="actions"> <div class="actions">
<button class = "button good" @click="goToHome"> <button
class="button good"
@click="goToHome"
>
Go to Home Go to Home
</button> </button>
<button class="button neutral" @click="goBack"> <button
class="button neutral"
@click="goBack"
>
Go Back Go Back
</button> </button>
</div> </div>

View File

@ -1,27 +1,58 @@
<template> <template>
<Section title="User Information" class="small"> <Section
<div v-if="!isLoggedIn" class="user-not-logged-in"> title="User Information"
class="small"
>
<div
v-if="!isLoggedIn"
class="user-not-logged-in"
>
<p>You are not currently logged in.</p> <p>You are not currently logged in.</p>
<p>To access user settings and logout, please <router-link to="/login">log in</router-link>.</p> <p>
To access user settings and logout, please <router-link to="/login">
log in
</router-link>.
</p>
</div> </div>
<div v-else class="user-control-panel"> <div
v-else
class="user-control-panel"
>
<dl class="user-info"> <dl class="user-info">
<dt>Username</dt> <dt>Username</dt>
<dd>{{ username }}</dd> <dd>{{ username }}</dd>
<dt v-if="userProvider !== 'system'">Provider</dt> <dt v-if="userProvider !== 'system'">
<dd v-if="userProvider !== 'system'">{{ userProvider }}</dd> Provider
<dt v-if="usergroup">Group</dt> </dt>
<dd v-if="usergroup">{{ usergroup }}</dd> <dd v-if="userProvider !== 'system'">
<dt v-if="acls && acls.length > 0">Matched ACLs</dt> {{ userProvider }}
</dd>
<dt v-if="usergroup">
Group
</dt>
<dd v-if="usergroup">
{{ usergroup }}
</dd>
<dt v-if="acls && acls.length > 0">
Matched ACLs
</dt>
<dd v-if="acls && acls.length > 0"> <dd v-if="acls && acls.length > 0">
<span class="acl-tag" v-for="(acl, idx) in acls" :key="`acl-${idx}`">{{ acl }}</span> <span
v-for="(acl, idx) in acls"
:key="`acl-${idx}`"
class="acl-tag"
>{{ acl }}</span>
</dd> </dd>
</dl> </dl>
<div class="user-actions"> <div class="user-actions">
<div class="action-buttons"> <div class="action-buttons">
<button @click="handleLogout" class="button bad" :disabled="loggingOut"> <button
class="button bad"
:disabled="loggingOut"
@click="handleLogout"
>
{{ loggingOut ? 'Logging out...' : 'Logout' }} {{ loggingOut ? 'Logging out...' : 'Logout' }}
</button> </button>
</div> </div>
@ -31,7 +62,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Section from 'picocrank/vue/components/Section.vue' import Section from 'picocrank/vue/components/Section.vue'
@ -102,12 +133,11 @@ async function handleLogout() {
} }
} }
let watchInterval = null const watchInterval = null
onMounted(() => { onMounted(() => {
updateUserInfo() updateUserInfo()
fetchWhoAmI() fetchWhoAmI()
}) })
onUnmounted(() => { onUnmounted(() => {

View File

@ -13,7 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"chai": "^6.2.2", "chai": "^6.2.2",
"eslint": "^10.5.0", "eslint": "^10.6.0",
"mocha": "^11.7.6", "mocha": "^11.7.6",
"selenium-webdriver": "^4.45.0" "selenium-webdriver": "^4.45.0"
} }
@ -783,9 +783,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "10.5.0", "version": "10.6.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
"integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [

View File

@ -12,7 +12,7 @@
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"devDependencies": { "devDependencies": {
"chai": "^6.2.2", "chai": "^6.2.2",
"eslint": "^10.5.0", "eslint": "^10.6.0",
"mocha": "^11.7.6", "mocha": "^11.7.6",
"selenium-webdriver": "^4.45.0" "selenium-webdriver": "^4.45.0"
}, },

View File

@ -1,11 +1,11 @@
module github.com/OliveTin/OliveTin/langtool module github.com/OliveTin/OliveTin/langtool
go 1.24.0 go 1.25.0
require ( require (
github.com/jamesread/golure v0.0.0-20260104005024-ad0d6ec8c0ac github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.9.4
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require golang.org/x/sys v0.40.0 // indirect require golang.org/x/sys v0.47.0 // indirect

View File

@ -1,28 +1,16 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jamesread/golure v0.0.0-20250919212919-976d085a100c h1:v8gN2xXFQjkF0PsoGSqDviRNmPHcBsvl6rMSbvXz1sM= github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da h1:hYsJqujd3A4Xtp9swe2d6Y6ij2ecd16E+i2oHAH/xaA=
github.com/jamesread/golure v0.0.0-20250919212919-976d085a100c/go.mod h1:BZ/CMtZJJ4LNEBDSjGfafTJMjlDPIA9FS16+reN9NUE= github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da/go.mod h1:BZ/CMtZJJ4LNEBDSjGfafTJMjlDPIA9FS16+reN9NUE=
github.com/jamesread/golure v0.0.0-20260104005024-ad0d6ec8c0ac h1:JQ6AC9tf2xhwTxMY9nuIeOPM7Cj0BDeCNgKPrNgQvtQ=
github.com/jamesread/golure v0.0.0-20260104005024-ad0d6ec8c0ac/go.mod h1:BZ/CMtZJJ4LNEBDSjGfafTJMjlDPIA9FS16+reN9NUE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -46,7 +46,7 @@ TOOL_GROUPS: list[tuple[str, frozenset[str]]] = [
), ),
( (
"Go codestyle (install via: make go-tools)", "Go codestyle (install via: make go-tools)",
frozenset({"gocyclo", "gocritic"}), frozenset({"golangci-lint"}),
), ),
( (
"Protocol buffers (install via: make -C service go-tools-all)", "Protocol buffers (install via: make -C service go-tools-all)",

25
service/.golangci.yml Normal file
View File

@ -0,0 +1,25 @@
version: "2"
run:
timeout: 5m
tests: true
linters:
default: none
enable:
- errcheck
- gocritic
- gocyclo
- ineffassign
- misspell
- staticcheck
- unconvert
- unused
linters-settings:
gocyclo:
min-complexity: 5
issues:
exclude-dirs:
- gen

View File

@ -30,8 +30,7 @@ compile: compile-armhf compile-x64-lin compile-x64-win
codestyle: go-tools codestyle: go-tools
go fmt ./... go fmt ./...
go vet ./... go vet ./...
gocyclo -over 4 internal golangci-lint run ./...
gocritic check ./...
test: unittests test: unittests
@ -54,13 +53,11 @@ find-flakey-tests-inf:
go run ./scripts/find-flakey-tests-inf go run ./scripts/find-flakey-tests-inf
go-tools: go-tools:
go install "github.com/fzipp/gocyclo/cmd/gocyclo" go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
go install "github.com/go-critic/go-critic/cmd/gocritic"
.PHONY: unittests unittests-fast find-flakey-tests find-flakey-tests-inf .PHONY: unittests unittests-fast find-flakey-tests find-flakey-tests-inf
go-tools-all: go-tools-all:
go install "github.com/bufbuild/buf/cmd/buf" go install "github.com/bufbuild/buf/cmd/buf"
go install "github.com/fzipp/gocyclo/cmd/gocyclo" go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
go install "github.com/go-critic/go-critic/cmd/gocritic"
go install "google.golang.org/protobuf/cmd/protoc-gen-go" go install "google.golang.org/protobuf/cmd/protoc-gen-go"

View File

@ -1,6 +1,6 @@
module github.com/OliveTin/OliveTin module github.com/OliveTin/OliveTin
go 1.25.10 go 1.26
exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
@ -10,36 +10,39 @@ require (
github.com/MicahParks/keyfunc/v3 v3.8.0 github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/PaesslerAG/jsonpath v0.1.1 github.com/PaesslerAG/jsonpath v0.1.1
github.com/alexedwards/argon2id v1.0.0 github.com/alexedwards/argon2id v1.0.0
github.com/bufbuild/buf v1.70.0 github.com/bufbuild/buf v1.71.0
github.com/expr-lang/expr v1.17.8
github.com/fsnotify/fsnotify v1.10.1 github.com/fsnotify/fsnotify v1.10.1
github.com/fzipp/gocyclo v0.6.0 github.com/go-viper/mapstructure/v2 v2.5.0
github.com/go-critic/go-critic v0.14.3
github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/golangci/golangci-lint/v2 v2.12.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da
github.com/knadh/koanf/parsers/yaml v1.1.0 github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.1 github.com/knadh/koanf/providers/file v1.2.1
github.com/knadh/koanf/providers/rawbytes v1.0.0 github.com/knadh/koanf/providers/rawbytes v1.0.0
github.com/knadh/koanf/v2 v2.3.4 github.com/knadh/koanf/v2 v2.3.5
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
go.akshayshah.org/connectproto v0.6.0 go.akshayshah.org/connectproto v0.6.0
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a golang.org/x/exp v0.0.0-20260611194520-c48552f49976
golang.org/x/oauth2 v0.36.0 golang.org/x/oauth2 v0.36.0
golang.org/x/sys v0.45.0 golang.org/x/sys v0.47.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 // indirect 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
4d63.com/gochecknoglobals v0.2.2 // indirect
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20260626152828-968bf0468096.1 // indirect
buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 // indirect buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260507063250-43b0c5a6cd08.1 // indirect buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260629211719-be09125d9193.1 // indirect
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260507063250-43b0c5a6cd08.1 // indirect buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260629211719-be09125d9193.1 // indirect
buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect
buf.build/go/app v0.2.1-0.20260407195847-833f8f978cda // indirect buf.build/go/app v0.2.1-0.20260407195847-833f8f978cda // indirect
buf.build/go/bufplugin v0.10.0 // indirect buf.build/go/bufplugin v0.10.0 // indirect
@ -50,33 +53,86 @@ require (
buf.build/go/spdx v0.2.0 // indirect buf.build/go/spdx v0.2.0 // indirect
buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 // indirect buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 // indirect
cel.dev/expr v0.25.2 // indirect cel.dev/expr v0.25.2 // indirect
charm.land/lipgloss/v2 v2.0.3 // indirect
codeberg.org/chavacava/garif v0.2.0 // indirect
codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect
connectrpc.com/otelconnect v0.9.0 // indirect connectrpc.com/otelconnect v0.9.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
dev.gaijin.team/go/golib v0.6.0 // indirect
github.com/4meepo/tagalign v1.4.3 // indirect
github.com/Abirdcfly/dupword v0.1.7 // indirect
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
github.com/Antonboom/errname v1.1.1 // indirect
github.com/Antonboom/nilnil v1.1.1 // indirect
github.com/Antonboom/testifylint v1.6.4 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect
github.com/Djarvur/go-err113 v0.1.1 // indirect
github.com/Masterminds/semver/v3 v3.5.0 // indirect
github.com/MicahParks/jwkset v0.11.0 // indirect github.com/MicahParks/jwkset v0.11.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/MirrexOne/unqueryvet v1.5.4 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/PaesslerAG/gval v1.2.4 // indirect github.com/PaesslerAG/gval v1.2.4 // indirect
github.com/alecthomas/chroma/v2 v2.24.1 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
github.com/alexkohler/prealloc v1.1.0 // indirect
github.com/alfatraining/structtag v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/ashanbrown/forbidigo/v2 v2.3.1 // indirect
github.com/ashanbrown/makezero/v2 v2.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bufbuild/protocompile v0.14.2-0.20260522222248-64e6ad034132 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
github.com/bombsimon/wsl/v5 v5.8.0 // indirect
github.com/breml/bidichk v0.3.3 // indirect
github.com/breml/errchkjson v0.4.1 // indirect
github.com/bufbuild/protocompile v0.14.2-0.20260605203730-cd7c3c124e10 // indirect
github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect
github.com/butuzov/ireturn v0.4.1 // indirect
github.com/butuzov/mirror v1.3.0 // indirect
github.com/catenacyber/perfsprint v0.10.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.11 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/ckaznocha/intrange v0.3.1 // indirect
github.com/cli/browser v1.3.0 // indirect github.com/cli/browser v1.3.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cristalhq/acmd v0.12.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/daixiang0/gci v0.13.7 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v29.5.2+incompatible // indirect github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/cli v29.6.1+incompatible // indirect
github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.8 // indirect
github.com/docker/docker-credential-helpers v0.9.7 // indirect
github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/expr-lang/expr v1.17.8 // indirect github.com/ettle/strcase v0.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fatih/color v1.19.0 // indirect
github.com/go-chi/chi/v5 v5.2.5 // indirect github.com/fatih/structtag v1.2.0 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/firefart/nonamedreturns v1.0.6 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.20 // indirect
github.com/go-critic/go-critic v0.14.4 // indirect
github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect
@ -84,83 +140,175 @@ require (
github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/pkgload v1.2.2 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/godoc-lint/godoc-lint v0.11.2 // indirect
github.com/gofrs/flock v0.13.0 // indirect github.com/gofrs/flock v0.13.0 // indirect
github.com/google/cel-go v0.28.1 // indirect github.com/golangci/asciicheck v0.5.0 // indirect
github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect
github.com/golangci/go-printf-func-name v0.1.1 // indirect
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
github.com/golangci/golines v0.15.0 // indirect
github.com/golangci/misspell v0.8.0 // indirect
github.com/golangci/plugin-module-register v0.1.2 // indirect
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
github.com/google/cel-go v0.29.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.21.6 // indirect github.com/google/go-containerregistry v0.21.7 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/gordonklaus/ineffassign v0.2.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.5.0 // indirect
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect github.com/jdx/go-netrc v1.0.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect github.com/jgautheron/goconst v1.10.0 // indirect
github.com/jjti/go-spancheck v0.6.5 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
github.com/kisielk/errcheck v1.10.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
github.com/klauspost/compress v1.19.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/kulti/thelper v0.7.1 // indirect
github.com/kunwardeep/paralleltest v1.0.15 // indirect
github.com/lasiar/canonicalheader v1.1.2 // indirect
github.com/ldez/exptostd v0.4.5 // indirect
github.com/ldez/gomoddirectives v0.8.0 // indirect
github.com/ldez/grignotin v0.10.1 // indirect
github.com/ldez/structtags v0.6.1 // indirect
github.com/ldez/tagliatelle v0.7.2 // indirect
github.com/ldez/usetesting v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/macabu/inamedparam v0.2.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
github.com/manuelarte/funcorder v0.6.0 // indirect
github.com/maratori/testableexamples v1.0.1 // indirect
github.com/maratori/testpackage v1.1.2 // indirect
github.com/matoous/godox v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mgechev/revive v1.15.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/moby/api v1.54.2 // indirect github.com/moby/moby/api v1.55.0 // indirect
github.com/moby/moby/client v0.4.1 // indirect github.com/moby/moby/client v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moricho/tparallel v0.3.2 // indirect
github.com/morikuni/aec v1.1.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pelletier/go-toml/v2 v2.3.1 // indirect
github.com/petermattis/goid v0.0.0-20260701081913-4f67fd55d3b4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/common v0.69.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect
github.com/quasilyte/go-ruleguard v0.4.5 // indirect github.com/quasilyte/go-ruleguard v0.4.5 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.1 // indirect github.com/quic-go/quic-go v0.60.0 // indirect
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect
github.com/segmentio/encoding v0.5.4 // indirect github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect
github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
github.com/securego/gosec/v2 v2.26.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sonatard/noctx v0.5.1 // indirect
github.com/sourcegraph/go-diff v0.8.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect github.com/spf13/viper v1.12.0 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tetafro/godot v1.5.6 // indirect
github.com/tetratelabs/wazero v1.12.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/btree v1.8.1 // indirect
github.com/vbatts/tar-split v0.12.3 // indirect github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect
go.lsp.dev/jsonrpc2 v0.10.0 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
go.lsp.dev/protocol v0.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
go.lsp.dev/uri v0.3.0 // indirect github.com/ultraware/funlen v0.2.0 // indirect
github.com/ultraware/whitespace v0.2.0 // indirect
github.com/uudashr/gocognit v1.2.1 // indirect
github.com/uudashr/iface v1.4.2 // indirect
github.com/xen0n/gosmopolitan v1.3.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.14.0 // indirect
go-simpler.org/sloglint v0.12.0 // indirect
go.augendre.info/arangolint v0.4.0 // indirect
go.augendre.info/fatcontext v0.9.0 // indirect
go.lsp.dev/jsonrpc2 v1.0.1 // indirect
go.lsp.dev/protocol v1.0.1 // indirect
go.lsp.dev/uri v1.0.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.uber.org/mock v0.6.0 // indirect go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect go.uber.org/zap v1.28.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.52.0 // indirect golang.org/x/crypto v0.53.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20260508232706-74f9aab9d74a // indirect golang.org/x/exp/typeparams v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/mod v0.36.0 // indirect golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.55.0 // indirect golang.org/x/net v0.56.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.22.0 // indirect
golang.org/x/term v0.43.0 // indirect golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.39.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.45.0 // indirect golang.org/x/tools v0.47.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
google.golang.org/grpc v1.79.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.7.0 // indirect
mvdan.cc/gofumpt v0.10.0 // indirect
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
mvdan.cc/xurls/v2 v2.6.0 // indirect mvdan.cc/xurls/v2 v2.6.0 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect pluginrpc.com/pluginrpc v0.5.0 // indirect
) )

File diff suppressed because it is too large Load Diff

View File

@ -134,7 +134,7 @@ func IsAllowedKill(cfg *config.Config, user *authpublic.AuthenticatedUser, actio
return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action) return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action)
} }
func isACLRelevantToAction(cfg *config.Config, actionAcls []string, acl *config.AccessControlList, user *authpublic.AuthenticatedUser) bool { func isACLRelevantToAction(actionAcls []string, acl *config.AccessControlList, user *authpublic.AuthenticatedUser) bool {
if !slices.Contains(user.Acls, acl.Name) { if !slices.Contains(user.Acls, acl.Name) {
// If the user does not have this ACL, then it is not relevant // If the user does not have this ACL, then it is not relevant
@ -156,7 +156,7 @@ func getRelevantAcls(cfg *config.Config, actionAcls []string, user *authpublic.A
var ret []*config.AccessControlList var ret []*config.AccessControlList
for _, acl := range cfg.AccessControlLists { for _, acl := range cfg.AccessControlLists {
if isACLRelevantToAction(cfg, actionAcls, acl, user) { if isACLRelevantToAction(actionAcls, acl, user) {
ret = append(ret, acl) ret = append(ret, acl)
} }
} }

View File

@ -46,7 +46,7 @@ func createKeys(t *testing.T) (*rsa.PrivateKey, string) {
if err != nil { if err != nil {
t.Fatalf("failed to create temp file: %v", err) t.Fatalf("failed to create temp file: %v", err)
} }
defer tmpFile.Close() defer func() { _ = tmpFile.Close() }()
t.Logf("Created File: %s", tmpFile.Name()) t.Logf("Created File: %s", tmpFile.Name())
@ -65,10 +65,6 @@ func newMux() *http.ServeMux {
return mux return mux
} }
func createJWTTokenWithExpiration(t *testing.T, privateKey *rsa.PrivateKey, expire int64) string {
return createJWTTokenWithExpirationAndAudience(t, privateKey, expire, "")
}
func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string { func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
token := jwt.New(jwt.SigningMethodRS256) token := jwt.New(jwt.SigningMethodRS256)
claims := token.Claims.(jwt.MapClaims) claims := token.Claims.(jwt.MapClaims)
@ -108,7 +104,7 @@ func setupJWTTestHandler(t *testing.T, cfg *config.Config) http.Handler {
} }
func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) { func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
defer res.Body.Close() defer func() { _ = res.Body.Close() }()
assert.Equal(t, expectCode, res.StatusCode) assert.Equal(t, expectCode, res.StatusCode)
body, _ := io.ReadAll(res.Body) body, _ := io.ReadAll(res.Body)
t.Logf("Response body: %s", string(body)) t.Logf("Response body: %s", string(body))
@ -120,7 +116,7 @@ func testJwkValidation(t *testing.T, expire int64, expectCode int) {
func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) { func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
privateKey, publicKeyPath := createKeys(t) privateKey, publicKeyPath := createKeys(t)
defer os.Remove(publicKeyPath) defer func() { _ = os.Remove(publicKeyPath) }()
cfg := config.DefaultConfig() cfg := config.DefaultConfig()
cfg.AuthJwtPubKeyPath = publicKeyPath cfg.AuthJwtPubKeyPath = publicKeyPath
@ -186,7 +182,7 @@ func makeJWTRequest(t *testing.T, srv *httptest.Server, tokenStr string) *http.R
func TestJWTHeader(t *testing.T) { func TestJWTHeader(t *testing.T) {
privateKey, publicKeyPath := createKeys(t) privateKey, publicKeyPath := createKeys(t)
defer os.Remove(publicKeyPath) defer func() { _ = os.Remove(publicKeyPath) }()
cfg := config.DefaultConfig() cfg := config.DefaultConfig()
cfg.AuthJwtPubKeyPath = publicKeyPath cfg.AuthJwtPubKeyPath = publicKeyPath
@ -217,7 +213,7 @@ func TestJWTHeader(t *testing.T) {
defer srv.Close() defer srv.Close()
res := makeJWTRequest(t, srv, tokenStr) res := makeJWTRequest(t, srv, tokenStr)
defer res.Body.Close() defer func() { _ = res.Body.Close() }()
assert.Equal(t, 200, res.StatusCode) assert.Equal(t, 200, res.StatusCode)
body, _ := io.ReadAll(res.Body) body, _ := io.ReadAll(res.Body)

View File

@ -354,7 +354,7 @@ func getUserInfo(cfg *config.Config, client *http.Client, provider *config.OAuth
return ret return ret
} }
defer res.Body.Close() defer func() { _ = res.Body.Close() }()
contents, err := io.ReadAll(res.Body) contents, err := io.ReadAll(res.Body)
@ -369,7 +369,7 @@ func getUserInfo(cfg *config.Config, client *http.Client, provider *config.OAuth
log.Debugf("OAuth2 User Data: %v+", string(contents)) log.Debugf("OAuth2 User Data: %v+", string(contents))
} }
err = json.Unmarshal([]byte(contents), &userData) err = json.Unmarshal(contents, &userData)
if err != nil { if err != nil {
log.Errorf("Failed to unmarshal user data: %v", err) log.Errorf("Failed to unmarshal user data: %v", err)

View File

@ -31,12 +31,12 @@ actions:
// Create temporary file // Create temporary file
tmpFile, err := os.CreateTemp("", "test_config_*.yaml") tmpFile, err := os.CreateTemp("", "test_config_*.yaml")
assert.NoError(t, err, "Should create temporary file") assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name()) defer func() { _ = os.Remove(tmpFile.Name()) }()
// Write test config to file // Write test config to file
_, err = tmpFile.WriteString(testConfig) _, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file") assert.NoError(t, err, "Should write test config to file")
tmpFile.Close() assert.NoError(t, tmpFile.Close())
// Load config using koanf // Load config using koanf
k := koanf.New(".") k := koanf.New(".")
@ -82,11 +82,11 @@ actions:
tmpFile, err := os.CreateTemp("", "test_config_empty_*.yaml") tmpFile, err := os.CreateTemp("", "test_config_empty_*.yaml")
assert.NoError(t, err, "Should create temporary file") assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name()) defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig) _, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file") assert.NoError(t, err, "Should write test config to file")
tmpFile.Close() assert.NoError(t, tmpFile.Close())
k := koanf.New(".") k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser()) err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
@ -117,11 +117,11 @@ actions:
tmpFile, err := os.CreateTemp("", "test_config_disabled_*.yaml") tmpFile, err := os.CreateTemp("", "test_config_disabled_*.yaml")
assert.NoError(t, err, "Should create temporary file") assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name()) defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig) _, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file") assert.NoError(t, err, "Should write test config to file")
tmpFile.Close() assert.NoError(t, tmpFile.Close())
k := koanf.New(".") k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser()) err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
@ -148,11 +148,11 @@ actions:
tmpFile, err := os.CreateTemp("", "test_config_no_auth_*.yaml") tmpFile, err := os.CreateTemp("", "test_config_no_auth_*.yaml")
assert.NoError(t, err, "Should create temporary file") assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name()) defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig) _, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file") assert.NoError(t, err, "Should write test config to file")
tmpFile.Close() assert.NoError(t, tmpFile.Close())
k := koanf.New(".") k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser()) err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())

View File

@ -982,7 +982,7 @@ func validatedSystemArgs(req *ExecutionRequest) (map[string]string, error) {
} }
func hasBindingAndAction(req *ExecutionRequest) bool { func hasBindingAndAction(req *ExecutionRequest) bool {
return !(req.Binding == nil || req.Binding.Action == nil) return req.Binding != nil && req.Binding.Action != nil
} }
func hasExec(req *ExecutionRequest) bool { func hasExec(req *ExecutionRequest) bool {

View File

@ -186,16 +186,6 @@ func (e *Executor) groupsHaveCapacityForQueued(req *ExecutionRequest) bool {
return true return true
} }
func firstFullGroupName(e *Executor, req *ExecutionRequest) string {
for _, limit := range actionGroupLimits(req) {
if e.countActiveInGroup(limit.name) >= (limit.maxConcurrent + 1) {
return limit.name
}
}
return ""
}
func firstFullGroupNameLocked(e *Executor, req *ExecutionRequest) string { func firstFullGroupNameLocked(e *Executor, req *ExecutionRequest) string {
for _, limit := range actionGroupLimits(req) { for _, limit := range actionGroupLimits(req) {
if e.countActiveInGroupLocked(limit.name) >= (limit.maxConcurrent + 1) { if e.countActiveInGroupLocked(limit.name) >= (limit.maxConcurrent + 1) {

View File

@ -55,7 +55,7 @@ func (tc *timeoutContext) setProcess(process *os.Process) {
tc.processMu.Unlock() tc.processMu.Unlock()
// If deadline already expired before process was set, kill now // If deadline already expired before process was set, kill now
if tc.Context.Err() == context.DeadlineExceeded && process != nil { if tc.Err() == context.DeadlineExceeded && process != nil {
logEntry := &InternalLogEntry{Process: process} logEntry := &InternalLogEntry{Process: process}
if err := tc.executor.Kill(logEntry); err != nil { if err := tc.executor.Kill(logEntry); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{

View File

@ -74,7 +74,7 @@ func watchPath(ctx *watchContext) {
return return
} }
defer watcher.Close() defer func() { _ = watcher.Close() }()
done := make(chan bool) done := make(chan bool)

View File

@ -15,8 +15,6 @@ func WriteFile(filename string, out []byte) {
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
handle, err := os.Create(filename) handle, err := os.Create(filename)
handle.Close()
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"error": err, "error": err,
@ -25,6 +23,7 @@ func WriteFile(filename string, out []byte) {
return return
} }
_ = handle.Close()
} }
err := os.WriteFile(filename, out, 0600) err := os.WriteFile(filename, out, 0600)

View File

@ -96,7 +96,7 @@ func getOsReleasePrettyName() string {
} }
} }
handle.Close() _ = handle.Close()
return "notfound" return "notfound"
} }

View File

@ -1,7 +1,6 @@
package servicehost package servicehost
import ( import (
"os"
"path/filepath" "path/filepath"
) )
@ -20,25 +19,3 @@ func resolveLogDirectory(dir string, baseDir string) string {
return filepath.Join(baseDir, dir) return filepath.Join(baseDir, dir)
} }
func executableDirectory() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", err
}
return filepath.Dir(ex), nil
}
func configuredServiceLogDirectory(dir string) (string, error) {
if dir == "" {
return "", nil
}
exeDir, err := executableDirectory()
if err != nil {
return "", err
}
return resolveLogDirectory(dir, exeDir), nil
}

View File

@ -0,0 +1,30 @@
//go:build windows
package servicehost
import (
"os"
"path/filepath"
)
func executableDirectory() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", err
}
return filepath.Dir(ex), nil
}
func configuredServiceLogDirectory(dir string) (string, error) {
if dir == "" {
return "", nil
}
exeDir, err := executableDirectory()
if err != nil {
return "", err
}
return resolveLogDirectory(dir, exeDir), nil
}

View File

@ -202,7 +202,7 @@ func appendFile(path, content string) error {
if err != nil { if err != nil {
return err return err
} }
defer file.Close() defer func() { _ = file.Close() }()
_, err = file.WriteString(content) _, err = file.WriteString(content)
return err return err

View File

@ -5,7 +5,6 @@ package tools
import ( import (
_ "github.com/bufbuild/buf/cmd/buf" _ "github.com/bufbuild/buf/cmd/buf"
_ "github.com/fzipp/gocyclo/cmd/gocyclo" _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
_ "github.com/go-critic/go-critic/cmd/gocritic"
_ "google.golang.org/protobuf/cmd/protoc-gen-go" _ "google.golang.org/protobuf/cmd/protoc-gen-go"
) )