fmt: Code style and linter
This commit is contained in:
parent
e2bf82ff9c
commit
25b80e1460
|
|
@ -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 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 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 codestyle` runs without any issues.
|
||||
- [ ] `make -wC service unittests` runs without any issues.
|
||||
|
|
|
|||
|
|
@ -38,32 +38,33 @@ repos:
|
|||
entry: make service-codestyle
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
files: ^(service/|proto/|lang/)
|
||||
|
||||
- id: frontend-codestyle
|
||||
name: frontend-codestyle
|
||||
entry: make frontend-codestyle
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
files: ^frontend/
|
||||
|
||||
- id: service-unittests
|
||||
name: service-unittests
|
||||
entry: make service-unittests
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
files: ^(service/|proto/|lang/)
|
||||
|
||||
- id: service-build
|
||||
name: service-build
|
||||
entry: make service
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
files: ^(service/|proto/|lang/)
|
||||
|
||||
- id: it
|
||||
name: integration-tests
|
||||
entry: make it
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
stages: [pre-push]
|
||||
files: ^(service/|frontend/|integration-tests/|proto/|Makefile)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
cd OliveTin
|
||||
pre-commit install
|
||||
pre-commit install --hook-type pre-push
|
||||
|
||||
# Step3: compile binary for current dev env (OS, ARCH)
|
||||
# `make proto` will also run `make go-tools`, which installs "buf". This binary
|
||||
|
|
|
|||
|
|
@ -4,12 +4,22 @@
|
|||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/recommended",
|
||||
"standard"
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"parser": {
|
||||
"js": "espree"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ endef
|
|||
|
||||
codestyle:
|
||||
npm install
|
||||
npx eslint --fix main.js js/*
|
||||
npx eslint --fix main.js js/* resources/vue
|
||||
npx stylelint style.css
|
||||
|
||||
clean:
|
||||
|
|
|
|||
|
|
@ -11,24 +11,25 @@
|
|||
"dependencies": {
|
||||
"@connectrpc/connect": "^2.1.2",
|
||||
"@connectrpc/connect-web": "^2.1.2",
|
||||
"@hugeicons/core-free-icons": "^4.2.1",
|
||||
"@hugeicons/vue": "^1.0.6",
|
||||
"@hugeicons/core-free-icons": "^4.2.2",
|
||||
"@hugeicons/vue": "^1.0.7",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"iconify-icon": "^3.0.2",
|
||||
"picocrank": "^1.17.0",
|
||||
"picocrank": "^1.18.2",
|
||||
"standard": "^17.1.2",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^8.0.16",
|
||||
"vue": "^3.5.38",
|
||||
"vite": "^8.1.3",
|
||||
"vue": "^3.5.39",
|
||||
"vue-i18n": "^11.4.6",
|
||||
"vue-router": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-vue": "^10.9.2",
|
||||
"process": "^0.11.10",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint": "^17.14.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -36,13 +37,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
|
|
@ -409,20 +410,20 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -430,9 +431,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -941,15 +942,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@hugeicons/core-free-icons": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.2.1.tgz",
|
||||
"integrity": "sha512-75jYZKYyA9VwS35YRmmGUGzFedbY+Fl0Vxx5FzXR2CGDlIhNRumFeVqaaKoClf2MeYEJwPAVMEL9RwCYtOgnSw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.2.2.tgz",
|
||||
"integrity": "sha512-fjQW3p6cwoJmwK3oCnV8Z3PC5sHihDvr2jkAHax8cxqp62GaV/D7B/Rnao+JwicW8fmLZUQ6QrzWfoyMFOEQlQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@hugeicons/vue": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.6.tgz",
|
||||
"integrity": "sha512-T1Wbuk2qDdoE2xcY3CDtwMOKewwIYDE7JoMVLeuAV4cbOx5CFtToHCQAmLgVPIFGGZUsV2nN52lBkts9upvePw==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.7.tgz",
|
||||
"integrity": "sha512-oUE0ztUNYbsMFpMgbV3bsxQBObNkeouomUThFuFPzYC5eV++VTYonStsltJu2MoLMkxV8mzpoVvIbuMPBZSN0w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^2.6.0 || ^3.0.0"
|
||||
|
|
@ -1110,13 +1111,13 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
|
||||
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
||||
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.2"
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
@ -1160,18 +1161,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"version": "0.139.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1185,9 +1186,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1201,9 +1202,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1217,9 +1218,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1233,9 +1234,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1249,9 +1250,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1265,9 +1266,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1281,9 +1282,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
|
@ -1297,9 +1298,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
|
@ -1313,9 +1314,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1329,9 +1330,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1345,9 +1346,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1361,27 +1362,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@napi-rs/wasm-runtime": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1395,9 +1396,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1436,15 +1437,31 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
|
||||
|
|
@ -1507,39 +1524,39 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz",
|
||||
"integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz",
|
||||
"integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@vue/shared": "3.5.38",
|
||||
"@vue/shared": "3.5.39",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz",
|
||||
"integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz",
|
||||
"integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/compiler-core": "3.5.39",
|
||||
"@vue/shared": "3.5.39"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz",
|
||||
"integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz",
|
||||
"integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@vue/compiler-core": "3.5.38",
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/compiler-ssr": "3.5.38",
|
||||
"@vue/shared": "3.5.38",
|
||||
"@vue/compiler-core": "3.5.39",
|
||||
"@vue/compiler-dom": "3.5.39",
|
||||
"@vue/compiler-ssr": "3.5.39",
|
||||
"@vue/shared": "3.5.39",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.15",
|
||||
|
|
@ -1547,13 +1564,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz",
|
||||
"integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz",
|
||||
"integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/compiler-dom": "3.5.39",
|
||||
"@vue/shared": "3.5.39"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
|
|
@ -1581,53 +1598,53 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz",
|
||||
"integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz",
|
||||
"integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/shared": "3.5.39"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz",
|
||||
"integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz",
|
||||
"integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/reactivity": "3.5.39",
|
||||
"@vue/shared": "3.5.39"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz",
|
||||
"integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz",
|
||||
"integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.38",
|
||||
"@vue/runtime-core": "3.5.38",
|
||||
"@vue/shared": "3.5.38",
|
||||
"@vue/reactivity": "3.5.39",
|
||||
"@vue/runtime-core": "3.5.39",
|
||||
"@vue/shared": "3.5.39",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz",
|
||||
"integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz",
|
||||
"integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/compiler-ssr": "3.5.39",
|
||||
"@vue/shared": "3.5.39"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.38"
|
||||
"vue": "3.5.39"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz",
|
||||
"integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz",
|
||||
"integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/addon-fit": {
|
||||
|
|
@ -1949,6 +1966,13 @@
|
|||
"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": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
|
|
@ -2138,9 +2162,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz",
|
||||
"integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==",
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
|
||||
"integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2990,6 +3014,51 @@
|
|||
"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": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
|
||||
|
|
@ -4897,6 +4966,19 @@
|
|||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -5186,19 +5268,19 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picocrank": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.17.0.tgz",
|
||||
"integrity": "sha512-EIUSI26elt6ulloN74CT2sX5tBxS2wjdV4A+eXHYNtHbH0hM2iKkYzpKKWOEr+yUTfrvjah6VuqieedFw/9R0w==",
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.18.2.tgz",
|
||||
"integrity": "sha512-YR1Y00KnEPjH6OCrijSxzTHoyHLhKrsPhkA7ubOkbvcUuD7wtVKbRxfPkjAkA838dGymjoG//iFca2oLnNT5tg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@hugeicons/core-free-icons": "^4.1.1",
|
||||
"@hugeicons/vue": "^1.0.5",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@hugeicons/core-free-icons": "^4.2.2",
|
||||
"@hugeicons/vue": "^1.0.7",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"femtocrank": "^2.5.0",
|
||||
"unplugin-vue-components": "^32.0.0",
|
||||
"vite": "^8.0.10",
|
||||
"vue": "^3.5.33",
|
||||
"vue-router": "^5.0.6"
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^8.1.2",
|
||||
"vue": "^3.5.39",
|
||||
"vue-router": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
|
|
@ -5318,9 +5400,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"version": "8.5.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
||||
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -5373,9 +5455,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
|
||||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
|
||||
"integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -5620,12 +5702,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@oxc-project/types": "=0.139.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -5635,21 +5717,21 @@
|
|||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
"@rolldown/binding-android-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
|
|
@ -6151,9 +6233,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/stylelint": {
|
||||
"version": "17.13.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.13.0.tgz",
|
||||
"integrity": "sha512-G1WYzMerp7ihOaIe9VJCHLt12MoAD2QLf1AFerYP37+BCRBUK5UCpq8e/mN+zCIaJPKQcaxhE4WlPmqdiOx/gw==",
|
||||
"version": "17.14.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.0.tgz",
|
||||
"integrity": "sha512-8xkHPpdqYryeIsOgfsYTmr6cIeC4nLYWk5S8BPxpodq8mIuepggkMljsHewWfuAjj/+qpRKou2QerhjMH3iasg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -6169,13 +6251,13 @@
|
|||
"dependencies": {
|
||||
"@csstools/css-calc": "^3.2.1",
|
||||
"@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/media-query-list-parser": "^5.0.0",
|
||||
"@csstools/selector-resolve-nested": "^4.0.0",
|
||||
"@csstools/selector-specificity": "^6.0.0",
|
||||
"colord": "^2.9.3",
|
||||
"cosmiconfig": "^9.0.1",
|
||||
"cosmiconfig": "^9.0.2",
|
||||
"css-functions-list": "^3.3.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"debug": "^4.4.3",
|
||||
|
|
@ -6195,7 +6277,7 @@
|
|||
"picocolors": "^1.1.1",
|
||||
"postcss": "^8.5.15",
|
||||
"postcss-safe-parser": "^7.0.1",
|
||||
"postcss-selector-parser": "^7.1.1",
|
||||
"postcss-selector-parser": "^7.1.4",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"string-width": "^8.2.1",
|
||||
"supports-hyperlinks": "^4.4.0",
|
||||
|
|
@ -6790,15 +6872,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
|
||||
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"postcss": "^8.5.16",
|
||||
"rolldown": "~1.1.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -6815,7 +6897,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@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",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
|
|
@ -6879,16 +6961,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz",
|
||||
"integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==",
|
||||
"version": "3.5.39",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz",
|
||||
"integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/compiler-sfc": "3.5.38",
|
||||
"@vue/runtime-dom": "3.5.38",
|
||||
"@vue/server-renderer": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
"@vue/compiler-dom": "3.5.39",
|
||||
"@vue/compiler-sfc": "3.5.39",
|
||||
"@vue/runtime-dom": "3.5.39",
|
||||
"@vue/server-renderer": "3.5.39",
|
||||
"@vue/shared": "3.5.39"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"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": {
|
||||
"version": "11.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz",
|
||||
|
|
@ -7145,6 +7319,16 @@
|
|||
"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": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"source": "index.html",
|
||||
"devDependencies": {
|
||||
"eslint-plugin-vue": "^10.9.2",
|
||||
"process": "^0.11.10",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint": "^17.14.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
@ -24,18 +25,18 @@
|
|||
"dependencies": {
|
||||
"@connectrpc/connect": "^2.1.2",
|
||||
"@connectrpc/connect-web": "^2.1.2",
|
||||
"@hugeicons/core-free-icons": "^4.2.1",
|
||||
"@hugeicons/vue": "^1.0.6",
|
||||
"@hugeicons/core-free-icons": "^4.2.2",
|
||||
"@hugeicons/vue": "^1.0.7",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"iconify-icon": "^3.0.2",
|
||||
"picocrank": "^1.17.0",
|
||||
"picocrank": "^1.18.2",
|
||||
"standard": "^17.1.2",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^8.0.16",
|
||||
"vue": "^3.5.38",
|
||||
"vite": "^8.1.3",
|
||||
"vue": "^3.5.39",
|
||||
"vue-i18n": "^11.4.6",
|
||||
"vue-router": "^5.1.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,34 +1,71 @@
|
|||
<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
|
||||
v-if="showExecutionIndicator"
|
||||
class="execution-indicator"
|
||||
:class="executionIndicatorClass"
|
||||
:title="executionIndicatorTitle"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<button :id="`actionButtonInner-${bindingId}`" :title="title" :disabled="!canExec || isDisabled"
|
||||
: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">
|
||||
/>
|
||||
<button
|
||||
:id="`actionButtonInner-${bindingId}`"
|
||||
:title="title"
|
||||
:disabled="!canExec || isDisabled"
|
||||
: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" />
|
||||
</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" />
|
||||
</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" />
|
||||
</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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ActionIconGlyph class="icon" :glyph="actionGlyph" />
|
||||
<span class="title" aria-live="polite">{{ displayTitle }}
|
||||
<ActionIconGlyph
|
||||
class="icon"
|
||||
:glyph="actionGlyph"
|
||||
/>
|
||||
<span
|
||||
class="title"
|
||||
aria-live="polite"
|
||||
>{{ displayTitle }}
|
||||
</span>
|
||||
<span v-if="rateLimitMessage" class="rate-limit-message">{{ rateLimitMessage }}</span>
|
||||
<span
|
||||
v-if="rateLimitMessage"
|
||||
class="rate-limit-message"
|
||||
>{{ rateLimitMessage }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -84,7 +121,6 @@ const displayTitle = ref('')
|
|||
|
||||
// State
|
||||
const isDisabled = ref(false)
|
||||
const showArgumentForm = ref(false)
|
||||
|
||||
// Rate limiting
|
||||
const rateLimitExpires = ref(0)
|
||||
|
|
@ -163,7 +199,7 @@ function consumeAndFlashPendingResult () {
|
|||
// Timestamps
|
||||
const updateIterationTimestamp = ref(0)
|
||||
|
||||
function constructFromJson(json) {
|
||||
function constructFromJson (json) {
|
||||
updateIterationTimestamp.value = 0
|
||||
|
||||
updateFromJson(json)
|
||||
|
|
@ -203,7 +239,7 @@ function constructFromJson(json) {
|
|||
updateRateLimitStatus()
|
||||
}
|
||||
|
||||
function updateFromJson(json) {
|
||||
function updateFromJson (json) {
|
||||
// Fields that should not be updated
|
||||
// title - as the callback URL relies on it
|
||||
|
||||
|
|
@ -219,7 +255,7 @@ function updateFromJson(json) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateRateLimitStatus() {
|
||||
function updateRateLimitStatus () {
|
||||
if (rateLimitExpires.value === 0) {
|
||||
isRateLimited.value = false
|
||||
rateLimitMessage.value = ''
|
||||
|
|
@ -257,7 +293,7 @@ function updateRateLimitStatus() {
|
|||
}
|
||||
}
|
||||
|
||||
function openActionDetails() {
|
||||
function openActionDetails () {
|
||||
const id = props.actionData?.bindingId
|
||||
if (!id) {
|
||||
return
|
||||
|
|
@ -265,7 +301,7 @@ function openActionDetails() {
|
|||
router.push(`/action/${id}`)
|
||||
}
|
||||
|
||||
async function handleClick() {
|
||||
async function handleClick () {
|
||||
if (popupOnStart.value === 'history') {
|
||||
openActionDetails()
|
||||
return
|
||||
|
|
@ -286,7 +322,7 @@ async function handleClick() {
|
|||
}
|
||||
}
|
||||
|
||||
function getUniqueId() {
|
||||
function getUniqueId () {
|
||||
if (window.isSecureContext) {
|
||||
return window.crypto.randomUUID()
|
||||
} else {
|
||||
|
|
@ -323,7 +359,7 @@ async function pollExecutionUntilDone (trackingId) {
|
|||
}
|
||||
}
|
||||
|
||||
async function startAction(actionArgs) {
|
||||
async function startAction (actionArgs) {
|
||||
buttonClasses.value = [] // Removes old animation classes
|
||||
|
||||
if (actionArgs === undefined) {
|
||||
|
|
@ -365,7 +401,7 @@ async function startAction(actionArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
function onLogEntryChanged(logEntry) {
|
||||
function onLogEntryChanged (logEntry) {
|
||||
if (logEntry.executionFinished) {
|
||||
onExecutionFinished(logEntry)
|
||||
} else if (logEntry.queued && !logEntry.executionStarted) {
|
||||
|
|
@ -375,12 +411,12 @@ function onLogEntryChanged(logEntry) {
|
|||
}
|
||||
}
|
||||
|
||||
function onExecutionQueued(_logEntry) {
|
||||
function onExecutionQueued (_logEntry) {
|
||||
isDisabled.value = true
|
||||
updateDom('action-queued', '[Queued]')
|
||||
}
|
||||
|
||||
function onExecutionStarted(logEntry) {
|
||||
function onExecutionStarted (logEntry) {
|
||||
if (
|
||||
popupOnStart.value &&
|
||||
popupOnStart.value.includes('execution-dialog') &&
|
||||
|
|
@ -393,7 +429,7 @@ function onExecutionStarted(logEntry) {
|
|||
updateDom(null, title.value)
|
||||
}
|
||||
|
||||
function onExecutionFinished(logEntry) {
|
||||
function onExecutionFinished (logEntry) {
|
||||
const trackingId = logEntry.executionTrackingId
|
||||
if (trackingId) {
|
||||
if (flashedTrackingIds.has(trackingId)) {
|
||||
|
|
@ -419,12 +455,12 @@ function onExecutionFinished(logEntry) {
|
|||
}
|
||||
}
|
||||
|
||||
function renderExecutionResult(resultCssClass, temporaryStatusMessage) {
|
||||
function renderExecutionResult (resultCssClass, temporaryStatusMessage) {
|
||||
updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
|
||||
onExecStatusChanged()
|
||||
}
|
||||
|
||||
function updateDom(resultCssClass, newTitle) {
|
||||
function updateDom (resultCssClass, newTitle) {
|
||||
if (resultCssClass == null) {
|
||||
buttonClasses.value = []
|
||||
} else {
|
||||
|
|
@ -434,7 +470,7 @@ function updateDom(resultCssClass, newTitle) {
|
|||
displayTitle.value = newTitle
|
||||
}
|
||||
|
||||
function onExecStatusChanged() {
|
||||
function onExecStatusChanged () {
|
||||
isDisabled.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -1,68 +1,141 @@
|
|||
<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>
|
||||
<div id="banner" v-if="bannerMessage" :style="bannerCss">
|
||||
<div
|
||||
v-if="bannerMessage"
|
||||
id="banner"
|
||||
:style="bannerCss"
|
||||
>
|
||||
<p>{{ bannerMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #user-info>
|
||||
<ConnectionBanner />
|
||||
<div class="flex-row user-info" style="gap: .5em;">
|
||||
<span id="link-login" v-if="!isLoggedIn && showLoginLink"><router-link to="/login">{{ t('login-button') }}</router-link></span>
|
||||
<router-link v-else to="/user" class="user-link" v-if="isLoggedIn">
|
||||
<div
|
||||
class="flex-row user-info"
|
||||
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>
|
||||
</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>
|
||||
|
||||
</template>
|
||||
</Header>
|
||||
|
||||
<div id="layout">
|
||||
<Navigation ref="navigation">
|
||||
<Sidebar ref="sidebar" id = "mainnav" v-if="sidebarEnabled && showNavigation"/>
|
||||
<Sidebar
|
||||
v-if="sidebarEnabled && showNavigation"
|
||||
id="mainnav"
|
||||
ref="sidebar"
|
||||
/>
|
||||
</Navigation>
|
||||
|
||||
<div id="content" initial-martial-complete="{{ hasLoaded }}">
|
||||
<div
|
||||
id="content"
|
||||
:initial-martial-complete="hasLoaded"
|
||||
>
|
||||
<main title="Main content">
|
||||
<router-view :key="$route.fullPath" />
|
||||
</main>
|
||||
|
||||
<footer title="footer" v-if="showFooter">
|
||||
<footer
|
||||
v-if="showFooter"
|
||||
title="footer"
|
||||
>
|
||||
<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>
|
||||
</p>
|
||||
<p>
|
||||
<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>
|
||||
<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>
|
||||
<a href="#" @click.prevent="openLanguageDialog">{{ currentLanguageName }}</a>
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="openLanguageDialog"
|
||||
>{{ currentLanguageName }}</a>
|
||||
</span>
|
||||
|
||||
<span v-if="availableThemes.length > 1">
|
||||
<a href="#" @click.prevent="openThemeDialog">{{ currentThemeName }}</a>
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="openThemeDialog"
|
||||
>{{ currentThemeName }}</a>
|
||||
</span>
|
||||
</p>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog ref="languageDialog" class="language-dialog" @click="handleLanguageDialogClick">
|
||||
<div class="dialog-content" @click.stop>
|
||||
<dialog
|
||||
ref="languageDialog"
|
||||
class="language-dialog"
|
||||
@click="handleLanguageDialogClick"
|
||||
>
|
||||
<div
|
||||
class="dialog-content"
|
||||
@click.stop
|
||||
>
|
||||
<h2>{{ t('language-dialog.title') }}</h2>
|
||||
<select v-model="selectedLanguage" @change="changeLanguage" class="language-select">
|
||||
<option v-for="(name, code) in availableLanguages" :key="code" :value="code">
|
||||
<select
|
||||
v-model="selectedLanguage"
|
||||
class="language-select"
|
||||
@change="changeLanguage"
|
||||
>
|
||||
<option
|
||||
v-for="(name, code) in availableLanguages"
|
||||
:key="code"
|
||||
:value="code"
|
||||
>
|
||||
{{ code === 'auto' ? name : `${name} (${code})` }}
|
||||
</option>
|
||||
</select>
|
||||
|
|
@ -72,55 +145,74 @@
|
|||
<span v-else>{{ t('language-dialog.not-available') }}</span>
|
||||
</p>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="closeLanguageDialog">{{ t('language-dialog.close') }}</button>
|
||||
<button @click="closeLanguageDialog">
|
||||
{{ t('language-dialog.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog ref="themeDialog" class="theme-dialog" @click="handleThemeDialogClick">
|
||||
<div class="dialog-content" @click.stop>
|
||||
<dialog
|
||||
ref="themeDialog"
|
||||
class="theme-dialog"
|
||||
@click="handleThemeDialogClick"
|
||||
>
|
||||
<div
|
||||
class="dialog-content"
|
||||
@click.stop
|
||||
>
|
||||
<h2>{{ t('theme-dialog.title') }}</h2>
|
||||
<select v-model="selectedTheme" @change="changeTheme" class="language-select">
|
||||
<option value="">{{ t('theme-dialog.default') }}</option>
|
||||
<option v-for="theme in availableThemes" :key="theme" :value="theme">
|
||||
<select
|
||||
v-model="selectedTheme"
|
||||
class="language-select"
|
||||
@change="changeTheme"
|
||||
>
|
||||
<option value="">
|
||||
{{ t('theme-dialog.default') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="theme in availableThemes"
|
||||
:key="theme"
|
||||
:value="theme"
|
||||
>
|
||||
{{ theme }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="dialog-buttons">
|
||||
<button @click="closeThemeDialog">{{ t('theme-dialog.close') }}</button>
|
||||
<button @click="closeThemeDialog">
|
||||
{{ t('theme-dialog.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Sidebar from 'picocrank/vue/components/Sidebar.vue';
|
||||
import Navigation from 'picocrank/vue/components/Navigation.vue';
|
||||
import Header from 'picocrank/vue/components/Header.vue';
|
||||
import ConnectionBanner from './components/ConnectionBanner.vue';
|
||||
import { connectEventStreamIfNeeded } from '../../js/websocket.js';
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Sidebar from 'picocrank/vue/components/Sidebar.vue'
|
||||
import Navigation from 'picocrank/vue/components/Navigation.vue'
|
||||
import Header from 'picocrank/vue/components/Header.vue'
|
||||
import ConnectionBanner from './components/ConnectionBanner.vue'
|
||||
import { connectEventStreamIfNeeded } from '../../js/websocket.js'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
||||
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
||||
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
import logoUrl from '../../OliveTinLogo.png';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import combinedTranslations from '../../../lang/combined_output.json';
|
||||
const { t, locale } = useI18n();
|
||||
import { UserCircle02Icon, DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
import logoUrl from '../../OliveTinLogo.png'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import combinedTranslations from '../../../lang/combined_output.json'
|
||||
const { t } = useI18n()
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter()
|
||||
|
||||
const sidebar = ref(null);
|
||||
const navigation = ref(null);
|
||||
const username = ref('notset');
|
||||
const isLoggedIn = ref(false);
|
||||
const currentVersion = ref('?');
|
||||
const pageTitle = ref('OliveTin');
|
||||
const bannerMessage = ref('');
|
||||
const bannerCss = ref('');
|
||||
const hasLoaded = ref(false);
|
||||
const sidebar = ref(null)
|
||||
const navigation = ref(null)
|
||||
const username = ref('notset')
|
||||
const isLoggedIn = ref(false)
|
||||
const currentVersion = ref('?')
|
||||
const pageTitle = ref('OliveTin')
|
||||
const bannerMessage = ref('')
|
||||
const bannerCss = ref('')
|
||||
const hasLoaded = ref(false)
|
||||
const showFooter = ref(true)
|
||||
const showNavigation = ref(true)
|
||||
const showLogs = ref(true)
|
||||
|
|
@ -144,8 +236,8 @@ const selectedTheme = ref(themePreference.value)
|
|||
|
||||
// Available languages with display names
|
||||
const availableLanguages = {
|
||||
'auto': 'Browser Language',
|
||||
'en': 'English',
|
||||
auto: 'Browser Language',
|
||||
en: 'English',
|
||||
'de-DE': 'Deutsch',
|
||||
'es-ES': 'Español',
|
||||
'it-IT': 'Italiano',
|
||||
|
|
@ -155,7 +247,7 @@ const availableLanguages = {
|
|||
// Computed property to get current language display name
|
||||
const currentLanguageName = computed(() => {
|
||||
if (languagePreference.value === 'auto') {
|
||||
return availableLanguages['auto']
|
||||
return availableLanguages.auto
|
||||
}
|
||||
|
||||
return availableLanguages[languagePreference.value] || languagePreference.value
|
||||
|
|
@ -178,7 +270,7 @@ const sidebarEnabled = computed(() => {
|
|||
return sectionNavigationStyle.value !== 'topbar' && showNavigation.value
|
||||
})
|
||||
|
||||
function normalizeBrowserLanguage() {
|
||||
function normalizeBrowserLanguage () {
|
||||
const available = Object.keys(combinedTranslations.messages || {})
|
||||
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
|
|
@ -202,13 +294,13 @@ function normalizeBrowserLanguage() {
|
|||
return 'en'
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
function toggleSidebar () {
|
||||
if (sidebar.value && showNavigation.value) {
|
||||
sidebar.value.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
function updateHeaderFromInit() {
|
||||
function updateHeaderFromInit () {
|
||||
if (!window.initResponse) {
|
||||
return
|
||||
}
|
||||
|
|
@ -246,7 +338,7 @@ function updateHeaderFromInit() {
|
|||
connectEventStreamIfNeeded()
|
||||
}
|
||||
|
||||
function renderNavigation() {
|
||||
function renderNavigation () {
|
||||
if (!navigation.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -263,7 +355,7 @@ function renderNavigation() {
|
|||
name: rootDashboard,
|
||||
title: rootDashboard,
|
||||
path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
|
||||
icon: DashboardSquare01Icon,
|
||||
icon: DashboardSquare01Icon
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +371,7 @@ function renderNavigation() {
|
|||
}
|
||||
}
|
||||
|
||||
function openLanguageDialog() {
|
||||
function openLanguageDialog () {
|
||||
selectedLanguage.value = languagePreference.value
|
||||
|
||||
if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) {
|
||||
|
|
@ -293,13 +385,13 @@ function openLanguageDialog() {
|
|||
}
|
||||
}
|
||||
|
||||
function closeLanguageDialog() {
|
||||
function closeLanguageDialog () {
|
||||
if (languageDialog.value) {
|
||||
languageDialog.value.close()
|
||||
}
|
||||
}
|
||||
|
||||
function changeLanguage() {
|
||||
function changeLanguage () {
|
||||
if (!window.i18n || !selectedLanguage.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -322,14 +414,14 @@ function changeLanguage() {
|
|||
closeLanguageDialog()
|
||||
}
|
||||
|
||||
function handleLanguageDialogClick(event) {
|
||||
function handleLanguageDialogClick (event) {
|
||||
// Close dialog when clicking on the backdrop
|
||||
if (event.target === languageDialog.value) {
|
||||
closeLanguageDialog()
|
||||
}
|
||||
}
|
||||
|
||||
function openThemeDialog() {
|
||||
function openThemeDialog () {
|
||||
selectedTheme.value = themePreference.value || ''
|
||||
|
||||
if (themeDialog.value) {
|
||||
|
|
@ -337,13 +429,13 @@ function openThemeDialog() {
|
|||
}
|
||||
}
|
||||
|
||||
function closeThemeDialog() {
|
||||
function closeThemeDialog () {
|
||||
if (themeDialog.value) {
|
||||
themeDialog.value.close()
|
||||
}
|
||||
}
|
||||
|
||||
function changeTheme() {
|
||||
function changeTheme () {
|
||||
if (!selectedTheme.value || selectedTheme.value === '') {
|
||||
localStorage.removeItem('olivetin-theme')
|
||||
themePreference.value = ''
|
||||
|
|
@ -356,7 +448,7 @@ function changeTheme() {
|
|||
closeThemeDialog()
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
function applyTheme () {
|
||||
let themeStyle = document.getElementById('theme-style')
|
||||
|
||||
if (!themeStyle) {
|
||||
|
|
@ -370,11 +462,11 @@ function applyTheme() {
|
|||
if (themePreference.value && themePreference.value !== '') {
|
||||
themeStyle.textContent = `@import url('/custom-webui/themes/${themePreference.value}/theme.css') layer(theme);`
|
||||
} else {
|
||||
themeStyle.textContent = `@import url('/theme.css') layer(theme);`
|
||||
themeStyle.textContent = '@import url(\'/theme.css\') layer(theme);'
|
||||
}
|
||||
}
|
||||
|
||||
function loadCustomJsIfEnabled() {
|
||||
function loadCustomJsIfEnabled () {
|
||||
if (!window.initResponse?.enableCustomJs || document.getElementById('olivetin-custom-js')) {
|
||||
return
|
||||
}
|
||||
|
|
@ -385,7 +477,7 @@ function loadCustomJsIfEnabled() {
|
|||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
function applyStyleMods() {
|
||||
function applyStyleMods () {
|
||||
if (!window.initResponse || !window.initResponse.styleMods) {
|
||||
return
|
||||
}
|
||||
|
|
@ -397,7 +489,7 @@ function applyStyleMods() {
|
|||
}
|
||||
}
|
||||
|
||||
function handleThemeDialogClick(event) {
|
||||
function handleThemeDialogClick (event) {
|
||||
if (event.target === themeDialog.value) {
|
||||
closeThemeDialog()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,85 @@
|
|||
<template>
|
||||
<section v-if="!dashboard && !initError" style = "text-align: center; padding: 2em;">
|
||||
<HugeiconsIcon :icon="Loading03Icon" width="3em" height="3em" style="animation: spin 1s linear infinite;" />
|
||||
<section
|
||||
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 style="color: var(--fg2);">{{ loadingTime }}s</p>
|
||||
<p style="color: var(--fg2);">
|
||||
{{ loadingTime }}s
|
||||
</p>
|
||||
</section>
|
||||
<section v-if="initError" style="text-align: center; padding: 2em;" class = "bad">
|
||||
<h2 style="color: var(--error);">Initialization Failed</h2>
|
||||
<section
|
||||
v-if="initError"
|
||||
style="text-align: center; padding: 2em;"
|
||||
class="bad"
|
||||
>
|
||||
<h2 style="color: var(--error);">
|
||||
Initialization Failed
|
||||
</h2>
|
||||
<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>
|
||||
<template v-else-if="dashboard">
|
||||
<section v-if="dashboard.contents.length == 0">
|
||||
<div class="back-button-container" v-if="isDirectory">
|
||||
<button @click="goBack" class="back-button">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<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 class="transparent" v-else>
|
||||
<div class="back-button-container" v-if="isDirectory">
|
||||
<button @click="goBack" class="back-button">
|
||||
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" />
|
||||
<section
|
||||
v-else
|
||||
class="transparent"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class = "dashboard-row" v-for="component in dashboard.contents" :key="component.title">
|
||||
<h2 v-if = "dashboard.title != 'Default'">
|
||||
<div
|
||||
v-for="component in dashboard.contents"
|
||||
:key="component.title"
|
||||
class="dashboard-row"
|
||||
>
|
||||
<h2 v-if="dashboard.title != 'Default'">
|
||||
<router-link
|
||||
v-if="component.entityType && component.entityKey"
|
||||
:to="{
|
||||
|
|
@ -39,14 +89,18 @@
|
|||
entityKey: component.entityKey
|
||||
}
|
||||
}"
|
||||
class="entity-link">
|
||||
class="entity-link"
|
||||
>
|
||||
{{ component.title }}
|
||||
</router-link>
|
||||
<span v-else>{{ component.title }}</span>
|
||||
</h2>
|
||||
|
||||
<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" />
|
||||
</template>
|
||||
</fieldset>
|
||||
|
|
@ -93,7 +147,7 @@ const isDirectory = computed(() => {
|
|||
return !rootDashboards.includes(dashboard.value.title) && dashboard.value.title !== 'Actions'
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
function goBack () {
|
||||
if (window.history.length > 1) {
|
||||
router.back()
|
||||
} else {
|
||||
|
|
@ -106,7 +160,7 @@ function goBack() {
|
|||
}
|
||||
}
|
||||
|
||||
async function getDashboard() {
|
||||
async function getDashboard () {
|
||||
const requestId = ++dashboardRequestId
|
||||
let title = props.title
|
||||
|
||||
|
|
@ -118,7 +172,7 @@ async function getDashboard() {
|
|||
|
||||
try {
|
||||
const request = {
|
||||
title: title,
|
||||
title
|
||||
}
|
||||
|
||||
if (props.entityType && props.entityKey) {
|
||||
|
|
@ -173,7 +227,7 @@ async function getDashboard() {
|
|||
}
|
||||
}
|
||||
|
||||
function waitForInitAndLoadDashboard() {
|
||||
function waitForInitAndLoadDashboard () {
|
||||
document.body.removeAttribute('loaded-dashboard')
|
||||
|
||||
if (loadingTimer) {
|
||||
|
|
@ -279,7 +333,6 @@ fieldset {
|
|||
place-items: stretch;
|
||||
}
|
||||
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
:id="`execution-${executionTrackingId}`"
|
||||
:id="`execution-${trackingId}`"
|
||||
class="execution-button"
|
||||
>
|
||||
<button
|
||||
|
|
@ -13,25 +13,26 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
//import { ExecutionFeedbackButton } from '../js/ExecutionFeedbackButton.js'
|
||||
// import { ExecutionFeedbackButton } from '../js/ExecutionFeedbackButton.js'
|
||||
|
||||
export default {
|
||||
name: 'ExecutionButton',
|
||||
// mixins: [ExecutionFeedbackButton],
|
||||
// mixins: [ExecutionFeedbackButton],
|
||||
props: {
|
||||
executionTrackingId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
trackingId: '',
|
||||
ellapsed: 0,
|
||||
isWaiting: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
buttonText () {
|
||||
if (this.isWaiting) {
|
||||
return 'Executing...'
|
||||
} else {
|
||||
|
|
@ -39,33 +40,34 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
mounted () {
|
||||
this.trackingId = this.executionTrackingId
|
||||
this.constructFromJson(this.executionTrackingId)
|
||||
},
|
||||
methods: {
|
||||
constructFromJson(json) {
|
||||
this.executionTrackingId = json
|
||||
constructFromJson (json) {
|
||||
this.trackingId = json
|
||||
this.ellapsed = 0
|
||||
this.isWaiting = true
|
||||
},
|
||||
|
||||
show() {
|
||||
show () {
|
||||
this.$emit('show')
|
||||
|
||||
if (window.executionDialog) {
|
||||
window.executionDialog.reset()
|
||||
window.executionDialog.show()
|
||||
window.executionDialog.fetchExecutionResult(this.executionTrackingId)
|
||||
window.executionDialog.fetchExecutionResult(this.trackingId)
|
||||
}
|
||||
},
|
||||
|
||||
onExecStatusChanged() {
|
||||
onExecStatusChanged () {
|
||||
this.isWaiting = false
|
||||
this.domTitle = this.ellapsed + 's'
|
||||
},
|
||||
|
||||
// Override from ExecutionFeedbackButton
|
||||
onExecutionFinished(logEntry) {
|
||||
onExecutionFinished (logEntry) {
|
||||
if (logEntry.timedOut) {
|
||||
this.renderExecutionResult('action-timeout', 'Timed out')
|
||||
} else if (logEntry.blocked) {
|
||||
|
|
@ -78,12 +80,12 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
renderExecutionResult(resultCssClass, temporaryStatusMessage) {
|
||||
renderExecutionResult (resultCssClass, temporaryStatusMessage) {
|
||||
this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
|
||||
this.onExecStatusChanged()
|
||||
},
|
||||
|
||||
updateDom(resultCssClass, title) {
|
||||
updateDom (resultCssClass, title) {
|
||||
// For execution button, we don't need to update classes as much
|
||||
// since it's a simpler component
|
||||
if (resultCssClass) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<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 }) }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,14 @@
|
|||
height="1em"
|
||||
class="action-icon-glyph-svg"
|
||||
/>
|
||||
<span v-else-if="decodedTextGlyphIsHtml" v-html="decodedTextGlyph"></span>
|
||||
<span v-else v-text="decodedTextGlyph"></span>
|
||||
<span
|
||||
v-else-if="decodedTextGlyphIsHtml"
|
||||
v-html="decodedTextGlyph"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
v-text="decodedTextGlyph"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
@ -22,15 +28,15 @@ const hugeiconsPrefix = 'hugeicons:'
|
|||
|
||||
/** Maps config values like hugeicons:CommandLineIcon to Hugeicons icon definitions. */
|
||||
const hugeiconsRegistry = {
|
||||
CommandLineIcon,
|
||||
CommandLineIcon
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
glyph: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const hugeiconsModel = computed(() => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
<template>
|
||||
<div :class = "statusClass + ' annotation'">
|
||||
<router-link v-if="showQueueLink" to="/logs/queue" class="queue-status-link">{{ statusText }}</router-link>
|
||||
<div :class="statusClass + ' annotation'">
|
||||
<router-link
|
||||
v-if="showQueueLink"
|
||||
to="/logs/queue"
|
||||
class="queue-status-link"
|
||||
>
|
||||
{{ statusText }}
|
||||
</router-link>
|
||||
<span v-else>{{ statusText }}</span><span>{{ exitCodeText }}</span>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
|
@ -20,7 +25,7 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
function isWaitingInQueue(logEntry) {
|
||||
function isWaitingInQueue (logEntry) {
|
||||
return logEntry &&
|
||||
!logEntry.executionFinished &&
|
||||
!logEntry.executionStarted
|
||||
|
|
@ -110,5 +115,4 @@ const statusClass = computed(() => {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,50 @@
|
|||
<template>
|
||||
<div id = "breadcrumbs">
|
||||
<template v-for="(link, index) in links" :key="link.name">
|
||||
<router-link :to="link.href">{{ link.name }}</router-link>
|
||||
<span v-if="index < links.length - 1" class="separator">
|
||||
<div id="breadcrumbs">
|
||||
<template
|
||||
v-for="(link, index) in links"
|
||||
:key="link.name"
|
||||
>
|
||||
<router-link :to="link.href">
|
||||
{{ link.name }}
|
||||
</router-link>
|
||||
<span
|
||||
v-if="index < links.length - 1"
|
||||
class="separator"
|
||||
>
|
||||
»
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</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>
|
||||
span {
|
||||
color: #bbb;
|
||||
|
|
@ -26,34 +62,3 @@ a:hover {
|
|||
}
|
||||
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
<template>
|
||||
<div class="choice-checklist" :id="wrapperId">
|
||||
<div
|
||||
:id="wrapperId"
|
||||
class="choice-checklist"
|
||||
>
|
||||
<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
|
||||
</button>
|
||||
<button type="button" class="choice-checklist-control" @click="selectNone">
|
||||
<button
|
||||
type="button"
|
||||
class="choice-checklist-control"
|
||||
@click="selectNone"
|
||||
>
|
||||
Select none
|
||||
</button>
|
||||
</div>
|
||||
<fieldset class="choice-checklist-fieldset">
|
||||
<legend class="visually-hidden">{{ label || name }}</legend>
|
||||
<legend class="visually-hidden">
|
||||
{{ label || name }}
|
||||
</legend>
|
||||
<label
|
||||
v-for="(choice, index) in choices"
|
||||
:key="choice.value"
|
||||
|
|
@ -21,7 +34,7 @@
|
|||
type="checkbox"
|
||||
:checked="isSelected(choice.value)"
|
||||
@change="handleToggle(choice.value)"
|
||||
/>
|
||||
>
|
||||
<span>{{ choiceLabel(choice) }}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
|
@ -34,7 +47,7 @@
|
|||
:required="required"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -86,23 +99,23 @@ function optionId (index) {
|
|||
return argumentFieldOptionId(props.name, index)
|
||||
}
|
||||
|
||||
function isSelected(value) {
|
||||
function isSelected (value) {
|
||||
return selectedValues.value.includes(value)
|
||||
}
|
||||
|
||||
function emitSelection(selected) {
|
||||
function emitSelection (selected) {
|
||||
emit('update:modelValue', formatChecklistValue(selected))
|
||||
}
|
||||
|
||||
function handleToggle(value) {
|
||||
function handleToggle (value) {
|
||||
emitSelection(toggleChoice(selectedValues.value, value))
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
function selectAll () {
|
||||
emitSelection(allChoiceValues(props.choices))
|
||||
}
|
||||
|
||||
function selectNone() {
|
||||
function selectNone () {
|
||||
emitSelection([])
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<template>
|
||||
<div class="choice-combobox" ref="rootRef">
|
||||
<div
|
||||
ref="rootRef"
|
||||
class="choice-combobox"
|
||||
>
|
||||
<input
|
||||
ref="searchInputRef"
|
||||
:id="id"
|
||||
ref="searchInputRef"
|
||||
type="text"
|
||||
class="choice-combobox-input"
|
||||
role="combobox"
|
||||
|
|
@ -17,12 +20,12 @@
|
|||
@input="handleSearchInput"
|
||||
@keydown="handleKeydown"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
>
|
||||
<input
|
||||
:name="name"
|
||||
type="hidden"
|
||||
:value="modelValue"
|
||||
/>
|
||||
>
|
||||
<ul
|
||||
v-if="isOpen && filteredChoices.length > 0"
|
||||
:id="listboxId"
|
||||
|
|
@ -44,7 +47,10 @@
|
|||
{{ choiceLabel(choice) }}
|
||||
</li>
|
||||
</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
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -140,11 +146,11 @@ watch([() => props.modelValue, () => props.choices], () => {
|
|||
}
|
||||
}, { immediate: true })
|
||||
|
||||
function choiceLabel(choice) {
|
||||
function choiceLabel (choice) {
|
||||
return choiceDisplayLabel(choice)
|
||||
}
|
||||
|
||||
function syncFromModelValue() {
|
||||
function syncFromModelValue () {
|
||||
const next = syncStateFromModelValue(props.choices, props.modelValue)
|
||||
query.value = next.query
|
||||
|
||||
|
|
@ -153,12 +159,12 @@ function syncFromModelValue() {
|
|||
}
|
||||
}
|
||||
|
||||
function selectedChoiceIndex(choices) {
|
||||
function selectedChoiceIndex (choices) {
|
||||
const index = choices.findIndex(choice => choice.value === props.modelValue)
|
||||
return index >= 0 ? index : 0
|
||||
}
|
||||
|
||||
function openList() {
|
||||
function openList () {
|
||||
document.dispatchEvent(new CustomEvent(closeOthersEvent, { detail: { id: props.id } }))
|
||||
const wasClosed = !isOpen.value
|
||||
isOpen.value = true
|
||||
|
|
@ -167,23 +173,23 @@ function openList() {
|
|||
}
|
||||
}
|
||||
|
||||
function closeList() {
|
||||
function closeList () {
|
||||
isOpen.value = false
|
||||
isUserFiltering.value = false
|
||||
syncFromModelValue()
|
||||
}
|
||||
|
||||
function emitValue(value) {
|
||||
function emitValue (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
function selectChoice(choice) {
|
||||
function selectChoice (choice) {
|
||||
emitValue(choice.value)
|
||||
query.value = choiceLabel(choice)
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
function handleFocus () {
|
||||
if (!isOpen.value) {
|
||||
syncFromModelValue()
|
||||
isUserFiltering.value = false
|
||||
|
|
@ -192,14 +198,14 @@ function handleFocus() {
|
|||
openList()
|
||||
}
|
||||
|
||||
function handleSearchInput(event) {
|
||||
function handleSearchInput (event) {
|
||||
isUserFiltering.value = true
|
||||
query.value = event.target.value
|
||||
openList()
|
||||
highlightedIndex.value = 0
|
||||
}
|
||||
|
||||
function moveHighlight(delta) {
|
||||
function moveHighlight (delta) {
|
||||
if (filteredChoices.value.length === 0) {
|
||||
return
|
||||
}
|
||||
|
|
@ -218,7 +224,7 @@ function moveHighlight(delta) {
|
|||
highlightedIndex.value = nextIndex
|
||||
}
|
||||
|
||||
function handleKeydown(event) {
|
||||
function handleKeydown (event) {
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
const wasOpen = isOpen.value
|
||||
|
|
@ -258,17 +264,17 @@ function handleKeydown(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
function handleBlur () {
|
||||
closeList()
|
||||
}
|
||||
|
||||
function handleCloseOthers(event) {
|
||||
function handleCloseOthers (event) {
|
||||
if (event.detail.id !== props.id) {
|
||||
closeList()
|
||||
}
|
||||
}
|
||||
|
||||
function handleOutsideMouseDown(event) {
|
||||
function handleOutsideMouseDown (event) {
|
||||
if (!isOpen.value || rootRef.value?.contains(event.target)) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
<template>
|
||||
<span id="connection-banner" v-if="connectionState.showDisconnectedBanner" class="inline-notification critical user-info-connection">
|
||||
<span class="connection-banner-sr-only" role="status">{{ staticAnnouncement }}</span>
|
||||
<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">
|
||||
<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>
|
||||
</template>
|
||||
|
|
@ -18,7 +30,7 @@ const websocketDocsUrl = 'https://docs.olivetin.app/troubleshooting/err-websocke
|
|||
|
||||
const linkText = computed(() => t('disconnected-banner-link-text'))
|
||||
|
||||
function formatShortRelative(ms) {
|
||||
function formatShortRelative (ms) {
|
||||
if (ms < 0) return '0s'
|
||||
const secs = Math.floor(ms / 1000)
|
||||
const mins = Math.floor(secs / 60)
|
||||
|
|
@ -28,7 +40,7 @@ function formatShortRelative(ms) {
|
|||
return `${secs}s`
|
||||
}
|
||||
|
||||
function formatShortTime(ts) {
|
||||
function formatShortTime (ts) {
|
||||
if (ts == null) return '--:--'
|
||||
return new Date(ts).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,31 @@
|
|||
<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-for="subcomponent in component.contents" :key="subcomponent.title">
|
||||
<template
|
||||
v-for="subcomponent in component.contents"
|
||||
:key="subcomponent.title"
|
||||
>
|
||||
<DashboardComponent :component="subcomponent" />
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -17,7 +34,6 @@
|
|||
OTHER: {{ component.type }}
|
||||
{{ component }}
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
|
@ -26,7 +42,7 @@ import DashboardComponentMostRecentExecution from './DashboardComponentMostRecen
|
|||
import DashboardComponentDirectory from './DashboardComponentDirectory.vue'
|
||||
import DashboardComponentDisplay from './DashboardComponentDisplay.vue'
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
<template>
|
||||
<button class="directory-button" :class="component.cssClass" @click="navigateToDirectory">
|
||||
<span class="icon" v-html="unicodeIcon"></span>
|
||||
<button
|
||||
class="directory-button"
|
||||
:class="component.cssClass"
|
||||
@click="navigateToDirectory"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
v-html="unicodeIcon"
|
||||
/>
|
||||
<span class="title">{{ component.title }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -18,7 +25,7 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
function getUnicodeIcon(icon) {
|
||||
function getUnicodeIcon (icon) {
|
||||
if (icon === '' || !icon) {
|
||||
return '📁' // Default folder icon
|
||||
} else {
|
||||
|
|
@ -30,7 +37,7 @@ const unicodeIcon = computed(() => {
|
|||
return getUnicodeIcon(props.component.icon)
|
||||
})
|
||||
|
||||
function navigateToDirectory() {
|
||||
function navigateToDirectory () {
|
||||
const params = { title: props.component.title }
|
||||
|
||||
if (props.component.entityType && props.component.entityKey) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="display" :class="component.cssClass">
|
||||
<div
|
||||
class="display"
|
||||
:class="component.cssClass"
|
||||
>
|
||||
<div v-html="component.title" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="mre-container" :class="component.cssClass">
|
||||
<div
|
||||
class="mre-container"
|
||||
:class="component.cssClass"
|
||||
>
|
||||
<router-link
|
||||
v-if="executionTrackingId"
|
||||
:to="`/logs/${executionTrackingId}`"
|
||||
|
|
@ -7,7 +10,10 @@
|
|||
>
|
||||
<pre class="mre-output">{{ output }}</pre>
|
||||
</router-link>
|
||||
<pre v-else class="mre-output fg-important">{{ output }}</pre>
|
||||
<pre
|
||||
v-else
|
||||
class="mre-output fg-important"
|
||||
>{{ output }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -26,7 +32,7 @@ const output = ref('Waiting...')
|
|||
const executionTrackingId = ref(null)
|
||||
let unwatchButtonResults = null
|
||||
|
||||
function updateFromLogEntry(logEntry) {
|
||||
function updateFromLogEntry (logEntry) {
|
||||
if (logEntry) {
|
||||
if (logEntry.output !== undefined) {
|
||||
output.value = logEntry.output
|
||||
|
|
@ -39,7 +45,7 @@ function updateFromLogEntry(logEntry) {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchMostRecentExecution() {
|
||||
async function fetchMostRecentExecution () {
|
||||
if (!props.component.title) {
|
||||
output.value = 'Error: No action ID specified'
|
||||
executionTrackingId.value = null
|
||||
|
|
|
|||
|
|
@ -3,44 +3,71 @@
|
|||
<template #title>
|
||||
<span class="section-title-with-icon">
|
||||
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 }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="hasTable" #toolbar>
|
||||
<template
|
||||
v-if="hasTable"
|
||||
#toolbar
|
||||
>
|
||||
<EntityListFilter v-model="searchText" />
|
||||
</template>
|
||||
|
||||
<p v-if="!hasTable">{{ definition.instances.length }} instances.</p>
|
||||
<p v-if="!hasTable">
|
||||
{{ definition.instances.length }} instances.
|
||||
</p>
|
||||
|
||||
<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
|
||||
v-else
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:instances="tableInstances"
|
||||
:properties="definition.properties"
|
||||
:total-instances="totalInstances"
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<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)">
|
||||
{{ inst.title }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="usedDashboards.length > 0" :class="{ padding: hasTable }">
|
||||
<div
|
||||
v-if="usedDashboards.length > 0"
|
||||
:class="{ padding: hasTable }"
|
||||
>
|
||||
<h3>Used on Dashboards:</h3>
|
||||
<ul>
|
||||
<li v-for="dash in usedDashboards" :key="dash">
|
||||
<li
|
||||
v-for="dash in usedDashboards"
|
||||
:key="dash"
|
||||
>
|
||||
<template v-if="isEntityDirectory(dash)">
|
||||
{{ getDashboardTitle(dash) }} <span class="entity-directory-label">[Entity Directory]</span>
|
||||
</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) }}
|
||||
</router-link>
|
||||
<span v-else>{{ dash }}</span>
|
||||
|
|
@ -51,58 +78,58 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import ActionIconGlyph from './ActionIconGlyph.vue'
|
||||
import EntityInstancesTable from './EntityInstancesTable.vue'
|
||||
import EntityListFilter from './EntityListFilter.vue'
|
||||
import { entityDetailsRoute } from '../utils/entityRoutes.js'
|
||||
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import ActionIconGlyph from './ActionIconGlyph.vue'
|
||||
import EntityInstancesTable from './EntityInstancesTable.vue'
|
||||
import EntityListFilter from './EntityListFilter.vue'
|
||||
import { entityDetailsRoute } from '../utils/entityRoutes.js'
|
||||
|
||||
const props = defineProps({
|
||||
const props = defineProps({
|
||||
definition: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const searchText = ref('')
|
||||
const tableInstances = ref([])
|
||||
const totalInstances = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const tableError = ref('')
|
||||
let fetchTimer = null
|
||||
let fetchSequence = 0
|
||||
const searchText = ref('')
|
||||
const tableInstances = ref([])
|
||||
const totalInstances = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const tableError = ref('')
|
||||
let fetchTimer = null
|
||||
let fetchSequence = 0
|
||||
|
||||
const hasTable = computed(() => (props.definition.properties?.length ?? 0) > 0)
|
||||
const hasTable = computed(() => (props.definition.properties?.length ?? 0) > 0)
|
||||
|
||||
const usedDashboards = computed(() => filteredDashboards(props.definition.usedOnDashboards ?? []))
|
||||
const usedDashboards = computed(() => filteredDashboards(props.definition.usedOnDashboards ?? []))
|
||||
|
||||
watch(searchText, () => {
|
||||
watch(searchText, () => {
|
||||
currentPage.value = 1
|
||||
scheduleFetchTableInstances()
|
||||
})
|
||||
})
|
||||
|
||||
watch([currentPage, pageSize], () => {
|
||||
watch([currentPage, pageSize], () => {
|
||||
scheduleFetchTableInstances()
|
||||
})
|
||||
})
|
||||
|
||||
function filteredDashboards(dashboards) {
|
||||
function filteredDashboards (dashboards) {
|
||||
return dashboards.filter(d => d && !d.includes('{{'))
|
||||
}
|
||||
}
|
||||
|
||||
function isEntityDirectory(dashboardTitle) {
|
||||
function isEntityDirectory (dashboardTitle) {
|
||||
return dashboardTitle.endsWith(' [Entity Directory]')
|
||||
}
|
||||
}
|
||||
|
||||
function getDashboardTitle(dashboardTitle) {
|
||||
function getDashboardTitle (dashboardTitle) {
|
||||
if (isEntityDirectory(dashboardTitle)) {
|
||||
return dashboardTitle.slice(0, -' [Entity Directory]'.length)
|
||||
}
|
||||
return dashboardTitle
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleFetchTableInstances() {
|
||||
function scheduleFetchTableInstances () {
|
||||
if (!hasTable.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -114,9 +141,9 @@
|
|||
fetchTimer = setTimeout(() => {
|
||||
fetchTableInstances()
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTableInstances() {
|
||||
async function fetchTableInstances () {
|
||||
if (!hasTable.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -148,20 +175,20 @@
|
|||
tableInstances.value = []
|
||||
totalInstances.value = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
if (hasTable.value) {
|
||||
fetchTableInstances()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
onBeforeUnmount(() => {
|
||||
if (fetchTimer) {
|
||||
clearTimeout(fetchTimer)
|
||||
fetchTimer = null
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -11,23 +11,26 @@
|
|||
</template>
|
||||
</Table>
|
||||
|
||||
<div v-if="totalInstances > 0" class="padding">
|
||||
<div
|
||||
v-if="totalInstances > 0"
|
||||
class="padding"
|
||||
>
|
||||
<Pagination
|
||||
:total="totalInstances"
|
||||
v-model:page="currentPageModel"
|
||||
v-model:page-size="pageSizeModel"
|
||||
:total="totalInstances"
|
||||
item-title="entities"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import Table from 'picocrank/vue/components/Table.vue'
|
||||
import Pagination from 'picocrank/vue/components/Pagination.vue'
|
||||
import { entityDetailsRoute } from '../utils/entityRoutes.js'
|
||||
import { computed } from 'vue'
|
||||
import Table from 'picocrank/vue/components/Table.vue'
|
||||
import Pagination from 'picocrank/vue/components/Pagination.vue'
|
||||
import { entityDetailsRoute } from '../utils/entityRoutes.js'
|
||||
|
||||
const props = defineProps({
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
type: Array,
|
||||
required: true
|
||||
|
|
@ -48,11 +51,11 @@
|
|||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:page', 'update:pageSize'])
|
||||
const emit = defineEmits(['update:page', 'update:pageSize'])
|
||||
|
||||
const headers = computed(() => {
|
||||
const headers = computed(() => {
|
||||
const propertyHeaders = props.properties.map(property => ({
|
||||
key: property.name,
|
||||
label: property.title
|
||||
|
|
@ -62,26 +65,26 @@
|
|||
{ key: 'title', label: 'Name' },
|
||||
...propertyHeaders
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
const tableRows = computed(() =>
|
||||
const tableRows = computed(() =>
|
||||
props.instances.map(instance => ({
|
||||
...instance.fields,
|
||||
title: instance.title,
|
||||
type: instance.type,
|
||||
uniqueKey: instance.uniqueKey
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
const currentPageModel = computed({
|
||||
const currentPageModel = computed({
|
||||
get: () => props.page,
|
||||
set: value => emit('update:page', value)
|
||||
})
|
||||
})
|
||||
|
||||
const pageSizeModel = computed({
|
||||
const pageSizeModel = computed({
|
||||
get: () => props.pageSize,
|
||||
set: value => emit('update:pageSize', value)
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,53 @@
|
|||
<template>
|
||||
<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">
|
||||
<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
|
||||
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 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>
|
||||
<input
|
||||
:value="modelValue"
|
||||
aria-label="Filter entities"
|
||||
placeholder="Filter entities..."
|
||||
@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>
|
||||
</button>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
defineEmits(['update:modelValue'])
|
||||
defineEmits(['update:modelValue'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<span class="log-action-title">
|
||||
<slot>{{ actionTitle }}</slot>
|
||||
<span v-if="justification" class="log-justification">— {{ justification }}</span>
|
||||
<span
|
||||
v-if="justification"
|
||||
class="log-justification"
|
||||
>— {{ justification }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||
|
||||
import Dashboard from './Dashboard.vue'
|
||||
|
||||
import { Wrench01Icon } 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'
|
||||
import { Wrench01Icon, LeftToRightListDashIcon, CellsIcon, DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
|
@ -44,8 +41,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Logs Calendar',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
{ name: "Calendar" },
|
||||
{ name: 'Logs', href: '/logs' },
|
||||
{ name: 'Calendar' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -56,8 +53,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Execution Queue',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
{ name: "Queue" },
|
||||
{ name: 'Logs', href: '/logs' },
|
||||
{ name: 'Queue' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -78,8 +75,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'OliveTin - Entity Details',
|
||||
breadcrumb: [
|
||||
{ name: "Entities", href: "/entities" },
|
||||
{ name: "Entity Details" }
|
||||
{ name: 'Entities', href: '/entities' },
|
||||
{ name: 'Entity Details' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -91,8 +88,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Execution',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
{ name: "Execution" },
|
||||
{ name: 'Logs', href: '/logs' },
|
||||
{ name: 'Execution' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -104,8 +101,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Action Details',
|
||||
breadcrumb: [
|
||||
{ name: "Actions", href: "/" },
|
||||
{ name: "Action Details" },
|
||||
{ name: 'Actions', href: '/' },
|
||||
{ name: 'Action Details' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -117,8 +114,8 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Execution conditions',
|
||||
breadcrumb: [
|
||||
{ name: "Actions", href: "/" },
|
||||
{ name: "Execution conditions" },
|
||||
{ name: 'Actions', href: '/' },
|
||||
{ name: 'Execution conditions' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -155,7 +152,7 @@ const routes = [
|
|||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
|
|
@ -168,7 +165,7 @@ const router = createRouter({
|
|||
router.beforeEach((to) => {
|
||||
if (to.meta && to.meta.title) {
|
||||
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
|
||||
document.title = to.meta.title + " - " + pageTitle
|
||||
document.title = to.meta.title + ' - ' + pageTitle
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
function parseLegacyChecklistValue(value) {
|
||||
function parseLegacyChecklistValue (value) {
|
||||
return value.split(',').map((segment) => segment.trim()).filter((segment) => segment !== '')
|
||||
}
|
||||
|
||||
export function parseChecklistValue(value) {
|
||||
export function parseChecklistValue (value) {
|
||||
if (!value || value === '') {
|
||||
return []
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ export function parseChecklistValue(value) {
|
|||
return parseLegacyChecklistValue(value)
|
||||
}
|
||||
|
||||
export function formatChecklistValue(selected) {
|
||||
export function formatChecklistValue (selected) {
|
||||
if (!Array.isArray(selected) || selected.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export function formatChecklistValue(selected) {
|
|||
return JSON.stringify(selected)
|
||||
}
|
||||
|
||||
export function toggleChoice(selected, value) {
|
||||
export function toggleChoice (selected, value) {
|
||||
const current = Array.isArray(selected) ? [...selected] : []
|
||||
const index = current.indexOf(value)
|
||||
|
||||
|
|
@ -45,10 +45,10 @@ export function toggleChoice(selected, value) {
|
|||
return current
|
||||
}
|
||||
|
||||
export function choiceLabel(choice) {
|
||||
export function choiceLabel (choice) {
|
||||
return choice.title || choice.value || ''
|
||||
}
|
||||
|
||||
export function allChoiceValues(choices) {
|
||||
export function allChoiceValues (choices) {
|
||||
return choices.map((choice) => choice.value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export function applyArgumentTemplate(template, args) {
|
||||
export function applyArgumentTemplate (template, args) {
|
||||
if (!template) {
|
||||
return ''
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ export function applyArgumentTemplate(template, args) {
|
|||
return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_, name) => args[name] ?? '')
|
||||
}
|
||||
|
||||
export function actionRequiresJustification(justification) {
|
||||
export function actionRequiresJustification (justification) {
|
||||
return (justification ?? '').length > 0
|
||||
}
|
||||
|
||||
export function actionJustificationTemplate(justification) {
|
||||
export function actionJustificationTemplate (justification) {
|
||||
return justification ?? ''
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@
|
|||
<template #title>
|
||||
<span class="section-title-with-icon">
|
||||
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 }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -12,14 +16,19 @@
|
|||
<button
|
||||
v-for="dashboard in backToDashboards"
|
||||
:key="dashboard.path"
|
||||
@click="goToDashboard(dashboard.path)"
|
||||
:title="'Back to ' + dashboard.title"
|
||||
class="button neutral"
|
||||
@click="goToDashboard(dashboard.path)"
|
||||
>
|
||||
<HugeiconsIcon :icon="DashboardSquare01Icon" />
|
||||
{{ dashboard.title }}
|
||||
</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" />
|
||||
Run
|
||||
</button>
|
||||
|
|
@ -34,20 +43,39 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<div class = "flex-row padding" v-if="action">
|
||||
<div class = "fg1">
|
||||
<div
|
||||
v-if="action"
|
||||
class="flex-row padding"
|
||||
>
|
||||
<div class="fg1">
|
||||
<dl>
|
||||
<dt>Timeout</dt>
|
||||
<dd>{{ action.timeout }} seconds</dd>
|
||||
|
||||
<template v-if="actionGroups.length > 0">
|
||||
<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>
|
||||
<dd>
|
||||
<ul class="action-group-list">
|
||||
<li 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
|
||||
<li
|
||||
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"
|
||||
:queue-size="group.queueSize"
|
||||
/>
|
||||
|
|
@ -56,22 +84,43 @@
|
|||
</dd>
|
||||
</template>
|
||||
</dl>
|
||||
<p class = "fg1">
|
||||
<p class="fg1">
|
||||
Execution history for this action. You can filter by execution tracking ID.
|
||||
</p>
|
||||
</div>
|
||||
<div style = "align-self: start; text-align: right;">
|
||||
<div style="align-self: start; text-align: right;">
|
||||
<div class="filter-container">
|
||||
<label class="input-with-icons">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" 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
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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>
|
||||
<input placeholder="Filter current page" v-model="searchText" />
|
||||
<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" />
|
||||
<input
|
||||
v-model="searchText"
|
||||
placeholder="Filter current page"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
</label>
|
||||
|
|
@ -91,9 +140,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="log in filteredLogs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle">
|
||||
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td>
|
||||
<td class="duration">{{ formatExecutionDuration(log) }}</td>
|
||||
<tr
|
||||
v-for="log in filteredLogs"
|
||||
:key="log.executionTrackingId"
|
||||
class="log-row"
|
||||
:title="log.actionTitle"
|
||||
>
|
||||
<td class="timestamp">
|
||||
{{ formatTimestamp(log.datetimeStarted) }}
|
||||
</td>
|
||||
<td class="duration">
|
||||
{{ formatExecutionDuration(log) }}
|
||||
</td>
|
||||
<td>
|
||||
<router-link :to="`/logs/${log.executionTrackingId}`">
|
||||
<LogActionTitle :justification="log.justification">
|
||||
|
|
@ -106,24 +164,47 @@
|
|||
<span class="annotation-key">User:</span>
|
||||
<span class="annotation-val">{{ log.user }}</span>
|
||||
</span>
|
||||
<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
|
||||
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>
|
||||
</td>
|
||||
<td class="exit-code">
|
||||
<ActionStatusDisplay :logEntry="log" :link-queued-status="true" />
|
||||
<ActionStatusDisplay
|
||||
:log-entry="log"
|
||||
:link-queued-status="true"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class="padding"
|
||||
@page-size-change="handlePageSizeChange" itemTitle="execution logs" />
|
||||
<Pagination
|
||||
: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 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>
|
||||
<router-link to="/">Return to index</router-link>
|
||||
<router-link to="/">
|
||||
Return to index
|
||||
</router-link>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
|
@ -174,16 +255,16 @@ const executionConditionCount = computed(() => countExecutionConditions(action.v
|
|||
|
||||
const actionGroups = computed(() => action.value?.groups ?? [])
|
||||
|
||||
async function fetchActionLogs() {
|
||||
async function fetchActionLogs () {
|
||||
loading.value = true
|
||||
try {
|
||||
const actionId = route.params.actionId
|
||||
const startOffset = (currentPage.value - 1) * pageSize.value
|
||||
|
||||
const args = {
|
||||
"actionId": actionId,
|
||||
"startOffset": BigInt(startOffset),
|
||||
"pageSize": BigInt(Number(pageSize.value)),
|
||||
actionId,
|
||||
startOffset: BigInt(startOffset),
|
||||
pageSize: BigInt(Number(pageSize.value))
|
||||
}
|
||||
|
||||
const response = await window.client.getActionLogs(args)
|
||||
|
|
@ -203,11 +284,11 @@ async function fetchActionLogs() {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchAction() {
|
||||
async function fetchAction () {
|
||||
try {
|
||||
const actionId = route.params.actionId
|
||||
const args = {
|
||||
"bindingId": actionId
|
||||
bindingId: actionId
|
||||
}
|
||||
const response = await window.client.getActionBinding(args)
|
||||
action.value = response.action
|
||||
|
|
@ -219,11 +300,11 @@ async function fetchAction() {
|
|||
}
|
||||
}
|
||||
|
||||
function goToDashboard(path) {
|
||||
function goToDashboard (path) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
function resetState () {
|
||||
action.value = null
|
||||
backToDashboards.value = []
|
||||
actionTitle.value = 'Action Details'
|
||||
|
|
@ -235,11 +316,11 @@ function resetState() {
|
|||
syncDurationTicker()
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
function clearSearch () {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
function formatTimestamp (timestamp) {
|
||||
if (!timestamp) return 'Unknown'
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
|
|
@ -249,11 +330,11 @@ function formatTimestamp(timestamp) {
|
|||
}
|
||||
}
|
||||
|
||||
function plural(n, singular, pluralForm) {
|
||||
function plural (n, singular, pluralForm) {
|
||||
return n === 1 ? `1 ${singular}` : `${n} ${pluralForm}`
|
||||
}
|
||||
|
||||
function formatDurationSimple(ms) {
|
||||
function formatDurationSimple (ms) {
|
||||
if (!Number.isFinite(ms) || ms < 0) {
|
||||
return '—'
|
||||
}
|
||||
|
|
@ -274,7 +355,7 @@ function formatDurationSimple(ms) {
|
|||
return parts.join(' ')
|
||||
}
|
||||
|
||||
function formatExecutionDuration(log) {
|
||||
function formatExecutionDuration (log) {
|
||||
// Reading durationClock keeps this column reactive while executions are in progress.
|
||||
const clock = durationClock.value
|
||||
|
||||
|
|
@ -300,7 +381,7 @@ function formatExecutionDuration(log) {
|
|||
return formatDurationSimple(endMs - started.getTime())
|
||||
}
|
||||
|
||||
function syncDurationTicker() {
|
||||
function syncDurationTicker () {
|
||||
if (durationTicker != null) {
|
||||
clearInterval(durationTicker)
|
||||
durationTicker = null
|
||||
|
|
@ -314,18 +395,18 @@ function syncDurationTicker() {
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
function handlePageChange (page) {
|
||||
currentPage.value = page
|
||||
fetchActionLogs()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(newPageSize) {
|
||||
function handlePageSizeChange (newPageSize) {
|
||||
pageSize.value = newPageSize
|
||||
currentPage.value = 1
|
||||
fetchActionLogs()
|
||||
}
|
||||
|
||||
async function startAction() {
|
||||
async function startAction () {
|
||||
if (!action.value || !action.value.bindingId) {
|
||||
console.error('Cannot start action: no binding ID')
|
||||
return
|
||||
|
|
@ -351,7 +432,7 @@ async function startAction() {
|
|||
}
|
||||
}
|
||||
|
||||
function onExecutionEvent(evt) {
|
||||
function onExecutionEvent (evt) {
|
||||
const logEntry = getExecutionLogEntry(evt)
|
||||
if (!logEntry || logEntry.bindingId !== route.params.actionId) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
<template>
|
||||
<Section :title="'Execution conditions: ' + actionTitle" :padding="false">
|
||||
<Section
|
||||
:title="'Execution conditions: ' + actionTitle"
|
||||
:padding="false"
|
||||
>
|
||||
<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
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<div v-if="action" class="padding content">
|
||||
<div
|
||||
v-if="action"
|
||||
class="padding content"
|
||||
>
|
||||
<p>
|
||||
These entries mirror the automatic triggers from your OliveTin configuration for this action.
|
||||
You can always run the action manually as well.
|
||||
|
|
@ -14,7 +23,12 @@
|
|||
|
||||
<h3 class="exec-type-heading">
|
||||
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>
|
||||
<p>
|
||||
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">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<p>Runs once when OliveTin starts.</p>
|
||||
</template>
|
||||
|
|
@ -32,37 +51,72 @@
|
|||
<template v-if="nonEmptyList(action.execOnCron)">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template v-if="nonEmptyList(action.execOnFileCreatedInDir)">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template v-if="nonEmptyList(action.execOnFileChangedInDir)">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template v-if="action.execOnCalendarFile">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<p><code>{{ action.execOnCalendarFile }}</code></p>
|
||||
</template>
|
||||
|
|
@ -70,10 +124,18 @@
|
|||
<template v-if="nonEmptyList(action.execOnWebhooks)">
|
||||
<h3 class="exec-type-heading">
|
||||
<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>
|
||||
<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.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span>
|
||||
<span v-if="nonEmptyObject(wh.matchHeaders)"> · matchHeaders: <code>{{ wh.matchHeaders }}</code></span>
|
||||
|
|
@ -83,14 +145,22 @@
|
|||
</ul>
|
||||
</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.
|
||||
</p>
|
||||
</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>
|
||||
<router-link :to="{ name: 'Actions' }">Return to index</router-link>
|
||||
<router-link :to="{ name: 'Actions' }">
|
||||
Return to index
|
||||
</router-link>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
|
@ -112,18 +182,18 @@ const execConditionDocs = {
|
|||
fileCreated: 'https://docs.olivetin.app/action_execution/onfilecreated.html',
|
||||
fileChanged: 'https://docs.olivetin.app/action_execution/onfilechanged.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) {
|
||||
return Array.isArray(list) && list.length > 0
|
||||
}
|
||||
|
||||
function nonEmptyObject(object) {
|
||||
function nonEmptyObject (object) {
|
||||
return object && Object.keys(object).length > 0
|
||||
}
|
||||
|
||||
function webhookHasCondition(webhook) {
|
||||
function webhookHasCondition (webhook) {
|
||||
return webhook.template || webhook.matchPath || nonEmptyObject(webhook.matchHeaders) || nonEmptyObject(webhook.matchQuery)
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +217,7 @@ const hasConfiguredTriggers = computed(() => {
|
|||
return false
|
||||
})
|
||||
|
||||
async function fetchAction() {
|
||||
async function fetchAction () {
|
||||
loading.value = true
|
||||
try {
|
||||
const actionId = route.params.actionId
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<section id = "argument-popup">
|
||||
<section id="argument-popup">
|
||||
<div class="section-header">
|
||||
<h2>
|
||||
<span class="section-title-with-icon">
|
||||
|
|
@ -8,7 +8,11 @@
|
|||
:to="`/action/${bindingId}`"
|
||||
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 }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
|
@ -17,48 +21,96 @@
|
|||
<div class="section-content padding">
|
||||
<form @submit="handleSubmit">
|
||||
<template v-if="actionArguments.length > 0">
|
||||
|
||||
<template v-for="arg in actionArguments" :key="arg.name">
|
||||
<label v-if="arg.type !== 'checklist'" :for="argumentFieldId(arg.name)">
|
||||
<template
|
||||
v-for="arg in actionArguments"
|
||||
:key="arg.name"
|
||||
>
|
||||
<label
|
||||
v-if="arg.type !== 'checklist'"
|
||||
:for="argumentFieldId(arg.name)"
|
||||
>
|
||||
{{ formatLabel(arg.title) }}
|
||||
</label>
|
||||
<div v-else class="argument-label">
|
||||
<div
|
||||
v-else
|
||||
class="argument-label"
|
||||
>
|
||||
{{ formatLabel(arg.title) }}
|
||||
</div>
|
||||
|
||||
<datalist 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">
|
||||
<datalist
|
||||
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 }}
|
||||
</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 }}
|
||||
</option>
|
||||
</datalist>
|
||||
|
||||
<ChoiceCombobox v-if="getInputComponent(arg) === 'select'" :id="argumentFieldId(arg.name)" :name="arg.name"
|
||||
:choices="arg.choices" :model-value="getArgumentValue(arg)" :required="arg.required"
|
||||
@update:model-value="handleChoiceUpdate(arg, $event)" />
|
||||
<ChoiceCombobox
|
||||
v-if="getInputComponent(arg) === 'select'"
|
||||
: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"
|
||||
:label="arg.title" :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"
|
||||
: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)"
|
||||
:checked="(arg.type === 'checkbox' || arg.type === 'confirmation') ? getArgumentValue(arg) : undefined"
|
||||
:list="(arg.suggestions || getBrowserSuggestions(arg).length > 0) ? argumentFieldChoicesId(arg.name) : undefined"
|
||||
:type="getInputComponent(arg) !== 'select' ? getInputType(arg) : undefined"
|
||||
:rows="arg.type === 'raw_string_multiline' ? 5 : undefined"
|
||||
:step="arg.type === 'datetime' ? 1 : undefined" :pattern="getPattern(arg)"
|
||||
@input="handleInput(arg, $event)" @change="handleChange(arg, $event)" />
|
||||
:step="arg.type === 'datetime' ? 1 : undefined"
|
||||
: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 v-if="justificationRequired">
|
||||
<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>
|
||||
|
||||
<div v-if="actionArguments.length === 0 && !justificationRequired">
|
||||
|
|
@ -66,10 +118,18 @@
|
|||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button name="start" type="submit" :disabled="!formReady || (hasConfirmation && !confirmationChecked)">
|
||||
<button
|
||||
name="start"
|
||||
type="submit"
|
||||
:disabled="!formReady || (hasConfirmation && !confirmationChecked)"
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
<button name="cancel" type="button" @click="handleCancel">
|
||||
<button
|
||||
name="cancel"
|
||||
type="button"
|
||||
@click="handleCancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -103,7 +163,7 @@ const router = useRouter()
|
|||
const dialog = ref(null)
|
||||
const title = ref('')
|
||||
const icon = ref('')
|
||||
//const arguments = ref([])
|
||||
// const arguments = ref([])
|
||||
const argValues = ref({})
|
||||
const confirmationChecked = ref(false)
|
||||
const hasConfirmation = ref(false)
|
||||
|
|
@ -128,7 +188,7 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
// Methods
|
||||
async function setup() {
|
||||
async function setup () {
|
||||
formReady.value = false
|
||||
document.body.removeAttribute('loaded-argument-form')
|
||||
|
||||
|
|
@ -202,7 +262,7 @@ async function setup() {
|
|||
}
|
||||
}
|
||||
|
||||
function formatLabel(title) {
|
||||
function formatLabel (title) {
|
||||
const lastChar = title.charAt(title.length - 1)
|
||||
if (lastChar === '?' || lastChar === '.' || lastChar === ':') {
|
||||
return title
|
||||
|
|
@ -210,7 +270,7 @@ function formatLabel(title) {
|
|||
return title + ':'
|
||||
}
|
||||
|
||||
function getInputComponent(arg) {
|
||||
function getInputComponent (arg) {
|
||||
if (arg.type === 'html') {
|
||||
return 'div'
|
||||
} else if (arg.type === 'raw_string_multiline') {
|
||||
|
|
@ -222,7 +282,7 @@ function getInputComponent(arg) {
|
|||
}
|
||||
}
|
||||
|
||||
function getInputType(arg) {
|
||||
function getInputType (arg) {
|
||||
if (arg.type === 'html' || arg.type === 'raw_string_multiline' || arg.type === 'select') {
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -242,33 +302,33 @@ function getInputType(arg) {
|
|||
return arg.type
|
||||
}
|
||||
|
||||
function getPattern(arg) {
|
||||
function getPattern (arg) {
|
||||
if (arg.type && arg.type.startsWith('regex:')) {
|
||||
return arg.type.replace('regex:', '')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getArgumentValue(arg) {
|
||||
function getArgumentValue (arg) {
|
||||
if (arg.type === 'checkbox' || arg.type === 'confirmation') {
|
||||
return argValues.value[arg.name] === '1' || argValues.value[arg.name] === true || argValues.value[arg.name] === 'true'
|
||||
}
|
||||
return argValues.value[arg.name] || ''
|
||||
}
|
||||
|
||||
function handleJustificationInput(event) {
|
||||
function handleJustificationInput (event) {
|
||||
justificationValue.value = event.target.value
|
||||
justificationEditedManually.value = true
|
||||
}
|
||||
|
||||
function handleInput(arg, event) {
|
||||
function handleInput (arg, event) {
|
||||
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value
|
||||
argValues.value[arg.name] = value
|
||||
updateUrlWithArg(arg.name, value)
|
||||
updateJustificationFromTemplate()
|
||||
}
|
||||
|
||||
function handleChange(arg, event) {
|
||||
function handleChange (arg, event) {
|
||||
if (arg.type === 'confirmation') {
|
||||
confirmationChecked.value = event.target.checked
|
||||
return
|
||||
|
|
@ -278,18 +338,18 @@ function handleChange(arg, event) {
|
|||
validateArgument(arg, event.target.value)
|
||||
}
|
||||
|
||||
function getValidationElement(arg) {
|
||||
function getValidationElement (arg) {
|
||||
return document.getElementById(argumentFieldValidationElementId(arg))
|
||||
}
|
||||
|
||||
function handleChoiceUpdate(arg, value) {
|
||||
function handleChoiceUpdate (arg, value) {
|
||||
argValues.value[arg.name] = value
|
||||
updateUrlWithArg(arg.name, value)
|
||||
validateArgument(arg, value)
|
||||
updateJustificationFromTemplate()
|
||||
}
|
||||
|
||||
async function validateArgument(arg, value) {
|
||||
async function validateArgument (arg, value) {
|
||||
if (!arg.type || arg.type.startsWith('regex:')) {
|
||||
return
|
||||
}
|
||||
|
|
@ -316,7 +376,7 @@ async function validateArgument(arg, value) {
|
|||
|
||||
try {
|
||||
const validateArgumentTypeArgs = {
|
||||
value: value,
|
||||
value,
|
||||
type: arg.type,
|
||||
bindingId: props.bindingId,
|
||||
argumentName: arg.name
|
||||
|
|
@ -348,7 +408,7 @@ async function validateArgument(arg, value) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateUrlWithArg(name, value) {
|
||||
function updateUrlWithArg (name, value) {
|
||||
if (name && value !== undefined) {
|
||||
const url = new URL(window.location.href)
|
||||
|
||||
|
|
@ -363,7 +423,7 @@ function updateUrlWithArg(name, value) {
|
|||
}
|
||||
}
|
||||
|
||||
function shouldSendArgument(arg) {
|
||||
function shouldSendArgument (arg) {
|
||||
if (!arg.name) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -371,7 +431,7 @@ function shouldSendArgument(arg) {
|
|||
return arg.type !== 'html'
|
||||
}
|
||||
|
||||
function formatArgumentValueForApi(arg, rawValue) {
|
||||
function formatArgumentValueForApi (arg, rawValue) {
|
||||
if (arg.type === 'checkbox' || arg.type === 'confirmation') {
|
||||
return rawValue === '1' || rawValue === true || rawValue === 'true' ? '1' : '0'
|
||||
}
|
||||
|
|
@ -387,7 +447,7 @@ function formatArgumentValueForApi(arg, rawValue) {
|
|||
return rawValue ?? ''
|
||||
}
|
||||
|
||||
function getSelectedArgumentEntries() {
|
||||
function getSelectedArgumentEntries () {
|
||||
const entries = []
|
||||
|
||||
for (const arg of actionArguments.value) {
|
||||
|
|
@ -404,17 +464,17 @@ function getSelectedArgumentEntries() {
|
|||
return entries
|
||||
}
|
||||
|
||||
function getArgumentValues() {
|
||||
function getArgumentValues () {
|
||||
return getSelectedArgumentEntries().map(({ name, value }) => ({ name, value }))
|
||||
}
|
||||
|
||||
function getArgumentMapForTemplate() {
|
||||
function getArgumentMapForTemplate () {
|
||||
return Object.fromEntries(
|
||||
getSelectedArgumentEntries().map(({ name, value }) => [name, value])
|
||||
)
|
||||
}
|
||||
|
||||
function updateJustificationFromTemplate() {
|
||||
function updateJustificationFromTemplate () {
|
||||
if (!justificationTemplate.value || justificationEditedManually.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -422,7 +482,7 @@ function updateJustificationFromTemplate() {
|
|||
justificationValue.value = applyArgumentTemplate(justificationTemplate.value, getArgumentMapForTemplate())
|
||||
}
|
||||
|
||||
function getUniqueId() {
|
||||
function getUniqueId () {
|
||||
if (window.isSecureContext) {
|
||||
return window.crypto.randomUUID()
|
||||
} else {
|
||||
|
|
@ -430,7 +490,7 @@ function getUniqueId() {
|
|||
}
|
||||
}
|
||||
|
||||
function getBrowserSuggestions(arg) {
|
||||
function getBrowserSuggestions (arg) {
|
||||
if (!arg.suggestionsBrowserKey) {
|
||||
return []
|
||||
}
|
||||
|
|
@ -448,7 +508,7 @@ function getBrowserSuggestions(arg) {
|
|||
return []
|
||||
}
|
||||
|
||||
function saveBrowserSuggestions() {
|
||||
function saveBrowserSuggestions () {
|
||||
for (const arg of actionArguments.value) {
|
||||
if (arg.suggestionsBrowserKey) {
|
||||
const value = argValues.value[arg.name]
|
||||
|
|
@ -484,7 +544,7 @@ function saveBrowserSuggestions() {
|
|||
}
|
||||
}
|
||||
|
||||
async function startAction(actionArgs) {
|
||||
async function startAction (actionArgs) {
|
||||
const startActionArgs = {
|
||||
bindingId: props.bindingId,
|
||||
arguments: actionArgs,
|
||||
|
|
@ -506,7 +566,7 @@ async function startAction(actionArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(event) {
|
||||
async function handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!formReady.value) {
|
||||
|
|
@ -568,24 +628,24 @@ async function handleSubmit(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
function handleCancel () {
|
||||
router.back()
|
||||
clearBookmark()
|
||||
}
|
||||
|
||||
function clearBookmark() {
|
||||
function clearBookmark () {
|
||||
window.history.replaceState({
|
||||
path: window.location.pathname
|
||||
}, '', window.location.pathname)
|
||||
}
|
||||
|
||||
function show() {
|
||||
function show () {
|
||||
if (dialog.value) {
|
||||
dialog.value.showModal()
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
function close () {
|
||||
if (dialog.value) {
|
||||
dialog.value.close()
|
||||
}
|
||||
|
|
@ -638,7 +698,6 @@ form {
|
|||
grid-template-columns: max-content auto auto;
|
||||
}
|
||||
|
||||
|
||||
.argument-description {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Section :title="t('diagnostics.get-support')">
|
||||
<p>{{ t('diagnostics.get-support-description') }}
|
||||
<p>
|
||||
{{ t('diagnostics.get-support-description') }}
|
||||
</p>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</Section>
|
||||
|
|
@ -21,26 +25,61 @@
|
|||
<Section :title="t('diagnostics.server-diagnostics')">
|
||||
<p>{{ t('diagnostics.server-diagnostics-description') }}</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>
|
||||
|
||||
<div role="toolbar">
|
||||
<button @click="generateServerDiagnostics" :disabled="loading" class = "good">{{ t('diagnostics.generate-server-diagnostics') }}</button>
|
||||
<button @click="copyServerDiagnostics" :disabled="!serverDiagnostics || loading" :class="serverDiagnosticsCopied ? 'good' : ''">{{ serverDiagnosticsCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}</button>
|
||||
<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>
|
||||
|
||||
<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 :title="t('diagnostics.browser-info')">
|
||||
<p>{{ t('diagnostics.browser-info-description') }}</p>
|
||||
|
||||
<div role="toolbar">
|
||||
<button @click="generateBrowserInfo" :disabled="loading" class = "good">{{ t('diagnostics.generate-browser-info') }}</button>
|
||||
<button @click="copyBrowserInfo" :disabled="!browserInfo || loading" :class="browserInfoCopied ? 'good' : ''">{{ browserInfoCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}</button>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
@ -58,17 +97,17 @@ const browserInfo = ref('')
|
|||
const serverDiagnosticsCopied = ref(false)
|
||||
const browserInfoCopied = ref(false)
|
||||
|
||||
async function fetchDiagnostics() {
|
||||
async function fetchDiagnostics () {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await window.client.getDiagnostics();
|
||||
const response = await window.client.getDiagnostics()
|
||||
diagnostics.value = {
|
||||
sshFoundKey: response.SshFoundKey,
|
||||
sshFoundConfig: response.SshFoundConfig
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch diagnostics:', err);
|
||||
console.error('Failed to fetch diagnostics:', err)
|
||||
diagnostics.value = {
|
||||
sshFoundKey: t('diagnostics.unknown'),
|
||||
sshFoundConfig: t('diagnostics.unknown')
|
||||
|
|
@ -77,20 +116,13 @@ async function fetchDiagnostics() {
|
|||
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()
|
||||
console.log("response", response)
|
||||
console.log('response', response)
|
||||
serverDiagnostics.value = `\`\`\`\n${response.alert}\n\`\`\`\n`
|
||||
}
|
||||
|
||||
async function copyServerDiagnostics() {
|
||||
async function copyServerDiagnostics () {
|
||||
try {
|
||||
await navigator.clipboard.writeText(serverDiagnostics.value)
|
||||
serverDiagnosticsCopied.value = true
|
||||
|
|
@ -102,7 +134,7 @@ async function copyServerDiagnostics() {
|
|||
}
|
||||
}
|
||||
|
||||
async function generateBrowserInfo() {
|
||||
async function generateBrowserInfo () {
|
||||
loading.value = true
|
||||
try {
|
||||
let userAgentData = 'N/A'
|
||||
|
|
@ -159,7 +191,7 @@ async function generateBrowserInfo() {
|
|||
})(),
|
||||
hardwareConcurrency: navigator.hardwareConcurrency || 'N/A',
|
||||
maxTouchPoints: navigator.maxTouchPoints || 'N/A',
|
||||
userAgentData: userAgentData
|
||||
userAgentData
|
||||
}
|
||||
|
||||
const showVersionNumber = window.initResponse?.effectivePolicy?.showVersionNumber ?? true
|
||||
|
|
@ -168,20 +200,20 @@ async function generateBrowserInfo() {
|
|||
: '[hidden]'
|
||||
const currentLanguage = locale.value || t('diagnostics.unknown')
|
||||
|
||||
let output = '';
|
||||
output += `\`\`\`\n`
|
||||
let output = ''
|
||||
output += '```\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 += `currentLanguage: ${currentLanguage}\n`
|
||||
output += `\n# Browser Information\n`
|
||||
output += '\n# Browser Information\n'
|
||||
output += `userAgent: ${info.userAgent}\n`
|
||||
output += `platform: ${info.platform}\n`
|
||||
output += `language: ${info.language}\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 += `\n# Display Information\n`
|
||||
output += '\n# Display Information\n'
|
||||
output += `screenWidth: ${info.screenWidth}\n`
|
||||
output += `screenHeight: ${info.screenHeight}\n`
|
||||
output += `screenColorDepth: ${info.screenColorDepth}\n`
|
||||
|
|
@ -189,18 +221,18 @@ async function generateBrowserInfo() {
|
|||
output += `viewportWidth: ${info.viewportWidth}\n`
|
||||
output += `viewportHeight: ${info.viewportHeight}\n`
|
||||
output += `devicePixelRatio: ${info.devicePixelRatio}\n`
|
||||
output += `\n# Feature Support\n`
|
||||
output += '\n# Feature Support\n'
|
||||
output += `cookieEnabled: ${info.cookieEnabled}\n`
|
||||
output += `localStorageEnabled: ${info.localStorageEnabled}\n`
|
||||
output += `sessionStorageEnabled: ${info.sessionStorageEnabled}\n`
|
||||
output += `onLine: ${info.onLine}\n`
|
||||
output += `hardwareConcurrency: ${info.hardwareConcurrency}\n`
|
||||
output += `maxTouchPoints: ${info.maxTouchPoints}\n`
|
||||
output += `\n# Location & Time\n`
|
||||
output += '\n# Location & Time\n'
|
||||
output += `timezone: ${info.timezone}\n`
|
||||
output += `timezoneOffset: ${info.timezoneOffset}\n`
|
||||
output += `\n### BROWSER INFO END (copy all text from BROWSER INFO START)`
|
||||
output += `\n\`\`\`\n`
|
||||
output += '\n### BROWSER INFO END (copy all text from BROWSER INFO START)'
|
||||
output += '\n```\n'
|
||||
|
||||
browserInfo.value = output
|
||||
} finally {
|
||||
|
|
@ -208,7 +240,7 @@ async function generateBrowserInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
async function copyBrowserInfo() {
|
||||
async function copyBrowserInfo () {
|
||||
try {
|
||||
await navigator.clipboard.writeText(browserInfo.value)
|
||||
browserInfoCopied.value = true
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<Section v-if="!definitionsLoaded" title="Loading entity definitions..." />
|
||||
<Section
|
||||
v-if="!definitionsLoaded"
|
||||
title="Loading entity definitions..."
|
||||
/>
|
||||
<Section
|
||||
v-else-if="totalInstances === 0"
|
||||
title="There are no entities to show yet."
|
||||
|
|
@ -18,21 +21,21 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import EntityDefinitionSection from '../components/EntityDefinitionSection.vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import EntityDefinitionSection from '../components/EntityDefinitionSection.vue'
|
||||
|
||||
const definitionsLoaded = ref(false)
|
||||
const entityDefinitions = ref([])
|
||||
const definitionsLoaded = ref(false)
|
||||
const entityDefinitions = ref([])
|
||||
|
||||
const totalInstances = computed(() =>
|
||||
const totalInstances = computed(() =>
|
||||
entityDefinitions.value.reduce(
|
||||
(sum, def) => sum + (def.instances?.length ?? 0),
|
||||
0,
|
||||
),
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
async function fetchEntities() {
|
||||
async function fetchEntities () {
|
||||
try {
|
||||
const ret = await window.client.getEntities()
|
||||
entityDefinitions.value = ret.entityDefinitions ?? []
|
||||
|
|
@ -43,9 +46,9 @@
|
|||
} finally {
|
||||
definitionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchEntities()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,24 @@
|
|||
<template #title>
|
||||
<span class="section-title-with-icon">
|
||||
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>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<button @click="goBack" class="back-button">
|
||||
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" />
|
||||
<button
|
||||
class="back-button"
|
||||
@click="goBack"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
:icon="ArrowLeftIcon"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
/>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -20,27 +31,48 @@
|
|||
<dl>
|
||||
<dt>Type</dt>
|
||||
<dd>
|
||||
<router-link :to="{ name: 'Entities' }" class="entity-type-link">
|
||||
<router-link
|
||||
:to="{ name: 'Entities' }"
|
||||
class="entity-type-link"
|
||||
>
|
||||
{{ entityType }}
|
||||
</router-link>
|
||||
</dd>
|
||||
<dt v-if="entityDetails.title">Title</dt>
|
||||
<dd v-if="entityDetails.title">{{ entityDetails.title }}</dd>
|
||||
<dt v-if="entityDetails.title">
|
||||
Title
|
||||
</dt>
|
||||
<dd v-if="entityDetails.title">
|
||||
{{ entityDetails.title }}
|
||||
</dd>
|
||||
<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>
|
||||
<dd>{{ value }}</dd>
|
||||
</template>
|
||||
</template>
|
||||
</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>
|
||||
</Section>
|
||||
|
||||
<Section v-if="entityDetails" title="Dashboard Entity Directories">
|
||||
<div v-if="filteredDirectories.length > 0" class="directories-section">
|
||||
<Section
|
||||
v-if="entityDetails"
|
||||
title="Dashboard Entity Directories"
|
||||
>
|
||||
<div
|
||||
v-if="filteredDirectories.length > 0"
|
||||
class="directories-section"
|
||||
>
|
||||
<ul class="directory-list">
|
||||
<li v-for="(directory, idx) in filteredDirectories" :key="idx">
|
||||
<li
|
||||
v-for="(directory, idx) in filteredDirectories"
|
||||
:key="idx"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Dashboard',
|
||||
|
|
@ -49,22 +81,36 @@
|
|||
entityType: entityType,
|
||||
entityKey: entityKey
|
||||
}
|
||||
}">
|
||||
}"
|
||||
>
|
||||
{{ directory }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p v-else>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 v-else>
|
||||
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>
|
||||
</Section>
|
||||
|
||||
<section v-if="entityDetails && relatedActions.length > 0" class="transparent">
|
||||
<section
|
||||
v-if="entityDetails && relatedActions.length > 0"
|
||||
class="transparent"
|
||||
>
|
||||
<div class="dashboard-row">
|
||||
<fieldset>
|
||||
<legend class="visually-hidden">Related actions</legend>
|
||||
<template v-for="(related, idx) in relatedActions" :key="related.action?.bindingId || idx">
|
||||
<legend class="visually-hidden">
|
||||
Related actions
|
||||
</legend>
|
||||
<template
|
||||
v-for="(related, idx) in relatedActions"
|
||||
:key="related.action?.bindingId || idx"
|
||||
>
|
||||
<ActionButton
|
||||
v-if="related.action"
|
||||
:action-data="related.action"
|
||||
|
|
@ -77,38 +123,38 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import ActionButton from '../ActionButton.vue'
|
||||
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import ActionButton from '../ActionButton.vue'
|
||||
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const entityDetails = ref(null)
|
||||
const router = useRouter()
|
||||
const entityDetails = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
const props = defineProps({
|
||||
entityType: String,
|
||||
entityKey: String
|
||||
})
|
||||
})
|
||||
|
||||
const filteredDirectories = computed(() => {
|
||||
const filteredDirectories = computed(() => {
|
||||
if (!entityDetails.value?.directories) {
|
||||
return []
|
||||
}
|
||||
return entityDetails.value.directories.filter(d => d)
|
||||
})
|
||||
})
|
||||
|
||||
const relatedActions = computed(() => entityDetails.value?.relatedActions ?? [])
|
||||
const relatedActions = computed(() => entityDetails.value?.relatedActions ?? [])
|
||||
|
||||
const entityIcon = computed(() => entityDetails.value?.icon ?? '')
|
||||
const entityIcon = computed(() => entityDetails.value?.icon ?? '')
|
||||
|
||||
function goBack() {
|
||||
function goBack () {
|
||||
router.push({ name: 'Entities' })
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchEntityDetails() {
|
||||
async function fetchEntityDetails () {
|
||||
try {
|
||||
const response = await window.client.getEntity({
|
||||
type: props.entityType,
|
||||
|
|
@ -120,11 +166,11 @@
|
|||
console.error('Failed to fetch entity details:', err)
|
||||
window.showBigError('fetch-entity-details', 'getting entity details', err, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchEntityDetails()
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,23 @@
|
|||
class="action-details-title-link"
|
||||
:title="titleTooltip"
|
||||
>
|
||||
<ActionIconGlyph class="action-title-icon" :glyph="icon" />
|
||||
<LogActionTitle v-if="logEntry" :action-title="title" :justification="logEntry.justification" />
|
||||
<ActionIconGlyph
|
||||
class="action-title-icon"
|
||||
:glyph="icon"
|
||||
/>
|
||||
<LogActionTitle
|
||||
v-if="logEntry"
|
||||
:action-title="title"
|
||||
:justification="logEntry.justification"
|
||||
/>
|
||||
<span v-else>{{ title }}</span>
|
||||
</router-link>
|
||||
<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>
|
||||
</template>
|
||||
</span>
|
||||
|
|
@ -23,73 +34,116 @@
|
|||
<button
|
||||
v-for="dashboard in backToDashboards"
|
||||
:key="dashboard.path"
|
||||
@click="goToDashboard(dashboard.path)"
|
||||
:title="'Back to ' + dashboard.title"
|
||||
class="button neutral"
|
||||
@click="goToDashboard(dashboard.path)"
|
||||
>
|
||||
<HugeiconsIcon :icon="DashboardSquare01Icon" />
|
||||
{{ dashboard.title }}
|
||||
</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" />
|
||||
Back
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div v-if="logEntry" class = "flex-row">
|
||||
<dl class = "fg1">
|
||||
<div
|
||||
v-if="logEntry"
|
||||
class="flex-row"
|
||||
>
|
||||
<dl class="fg1">
|
||||
<dt>Duration</dt>
|
||||
<dd><span v-html="duration"></span></dd>
|
||||
<dd><span v-html="duration" /></dd>
|
||||
|
||||
<dt>Status</dt>
|
||||
<dd class="execution-dialog-status">
|
||||
<ActionStatusDisplay :log-entry="logEntry" :link-queued-status="true" />
|
||||
<ActionStatusDisplay
|
||||
:log-entry="logEntry"
|
||||
:link-queued-status="true"
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div v-if="notFound" class="error-message padded-content">
|
||||
<div
|
||||
v-if="notFound"
|
||||
class="error-message padded-content"
|
||||
>
|
||||
<h3>Execution Not Found</h3>
|
||||
<p>{{ errorMessage }}</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 class="xterm-output-container">
|
||||
<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" />
|
||||
</button>
|
||||
<button type="button" class="xterm-overlay-button" @click="toggleSize" title="Toggle fullscreen">
|
||||
<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" />
|
||||
<button
|
||||
type="button"
|
||||
class="xterm-overlay-button"
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="xtermOutput"></div>
|
||||
<div ref="xtermOutput" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br>
|
||||
|
||||
<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" />
|
||||
Rerun
|
||||
</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" />
|
||||
Kill
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||
import LogActionTitle from '../components/LogActionTitle.vue'
|
||||
|
|
@ -138,7 +192,7 @@ const errorMessage = ref('')
|
|||
let executionTicker = null
|
||||
let terminal = null
|
||||
|
||||
function initializeTerminal() {
|
||||
function initializeTerminal () {
|
||||
terminal = new OutputTerminal(executionTrackingId.value)
|
||||
terminal.open(xtermOutput.value)
|
||||
terminal.resize(80, 40)
|
||||
|
|
@ -146,7 +200,7 @@ function initializeTerminal() {
|
|||
window.terminal = terminal
|
||||
}
|
||||
|
||||
function toggleSize() {
|
||||
function toggleSize () {
|
||||
if (!xtermOutput.value) {
|
||||
return
|
||||
}
|
||||
|
|
@ -162,7 +216,7 @@ function toggleSize() {
|
|||
}
|
||||
}
|
||||
|
||||
async function copyOutput() {
|
||||
async function copyOutput () {
|
||||
const text = terminal?.getBufferAsString?.() || logEntry.value?.output || ''
|
||||
if (!text) {
|
||||
return
|
||||
|
|
@ -175,7 +229,7 @@ async function copyOutput() {
|
|||
}
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
async function reset () {
|
||||
executionSeconds.value = 0
|
||||
executionTrackingId.value = 'notset'
|
||||
hideBasics.value = false
|
||||
|
|
@ -200,7 +254,7 @@ async function reset() {
|
|||
}
|
||||
}
|
||||
|
||||
function show(actionButton) {
|
||||
function show (actionButton) {
|
||||
if (actionButton) {
|
||||
icon.value = actionButton.glyph ?? ''
|
||||
}
|
||||
|
|
@ -219,7 +273,7 @@ function show(actionButton) {
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
async function rerunAction() {
|
||||
async function rerunAction () {
|
||||
const bindingId = logEntry.value?.bindingId
|
||||
if (!logEntry.value || !bindingId) {
|
||||
console.error('Cannot rerun: no action ID available')
|
||||
|
|
@ -247,7 +301,7 @@ async function rerunAction() {
|
|||
}
|
||||
}
|
||||
|
||||
async function killAction() {
|
||||
async function killAction () {
|
||||
if (!executionTrackingId.value || executionTrackingId.value === 'notset') {
|
||||
return
|
||||
}
|
||||
|
|
@ -263,20 +317,20 @@ async function killAction() {
|
|||
}
|
||||
}
|
||||
|
||||
function executionTick() {
|
||||
function executionTick () {
|
||||
executionSeconds.value++
|
||||
updateDuration(null)
|
||||
}
|
||||
|
||||
function hideEverythingApartFromOutput() {
|
||||
function hideEverythingApartFromOutput () {
|
||||
hideDetailsOnResult.value = true
|
||||
hideBasics.value = true
|
||||
hideDetailsOnResult.value = true
|
||||
hideBasics.value = true
|
||||
}
|
||||
|
||||
async function fetchExecutionResult(executionTrackingIdParam) {
|
||||
console.log("fetchExecutionResult", executionTrackingIdParam)
|
||||
async function fetchExecutionResult (executionTrackingIdParam) {
|
||||
console.log('fetchExecutionResult', executionTrackingIdParam)
|
||||
|
||||
executionTrackingId.value = executionTrackingIdParam
|
||||
notFound.value = false
|
||||
|
|
@ -303,11 +357,10 @@ async function fetchExecutionResult(executionTrackingIdParam) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateDuration(logEntryParam) {
|
||||
function updateDuration (logEntryParam) {
|
||||
logEntry.value = logEntryParam
|
||||
if (logEntry.value == null) {
|
||||
duration.value = executionSeconds.value + ' seconds'
|
||||
duration.value = duration.value
|
||||
} else if (!logEntry.value.executionStarted) {
|
||||
duration.value = logEntry.value.datetimeStarted + ' (request time). Not executed.'
|
||||
} else if (logEntry.value.executionStarted && !logEntry.value.executionFinished) {
|
||||
|
|
@ -327,7 +380,7 @@ function updateDuration(logEntryParam) {
|
|||
}
|
||||
}
|
||||
|
||||
async function renderExecutionResult(res) {
|
||||
async function renderExecutionResult (res) {
|
||||
logEntry.value = res.logEntry
|
||||
if (res.backToDashboards) {
|
||||
backToDashboards.value = res.backToDashboards.slice(0, 3)
|
||||
|
|
@ -362,11 +415,11 @@ async function renderExecutionResult(res) {
|
|||
}
|
||||
}
|
||||
|
||||
function renderError(err) {
|
||||
function renderError (err) {
|
||||
window.showBigError('execution-dlg-err', 'in the execution dialog', 'Failed to fetch execution result. ' + err, false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
function handleClose () {
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
|
|
@ -374,7 +427,7 @@ function handleClose() {
|
|||
executionTicker = null
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
function cleanup () {
|
||||
if (executionTicker) {
|
||||
clearInterval(executionTicker)
|
||||
}
|
||||
|
|
@ -385,11 +438,11 @@ function cleanup() {
|
|||
terminal = null
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
function goBack () {
|
||||
router.back()
|
||||
}
|
||||
|
||||
function goToDashboard(path) {
|
||||
function goToDashboard (path) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
|
|
@ -418,7 +471,6 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,78 @@
|
|||
<template>
|
||||
<Section title="Login to OliveTin" class="small">
|
||||
<Section
|
||||
title="Login to OliveTin"
|
||||
class="small"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div v-if="hasOAuth" class="login-oauth2">
|
||||
<div
|
||||
v-if="hasOAuth"
|
||||
class="login-oauth2"
|
||||
>
|
||||
<h3>OAuth Login</h3>
|
||||
<div class="oauth-providers">
|
||||
<button v-for="provider in oauthProviders" :key="provider.key" class="oauth-button"
|
||||
@click="loginWithOAuth(provider)">
|
||||
<span v-if="provider.icon" class="provider-icon" v-html="provider.icon"></span>
|
||||
<button
|
||||
v-for="provider in oauthProviders"
|
||||
: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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLocalLogin" class="login-local">
|
||||
<div
|
||||
v-if="hasLocalLogin"
|
||||
class="login-local"
|
||||
>
|
||||
<h3>Local Login</h3>
|
||||
<form @submit.prevent="handleLocalLogin" class="local-login-form">
|
||||
<div v-if="loginError" class="bad">
|
||||
<form
|
||||
class="local-login-form"
|
||||
@submit.prevent="handleLocalLogin"
|
||||
>
|
||||
<div
|
||||
v-if="loginError"
|
||||
class="bad"
|
||||
>
|
||||
{{ loginError }}
|
||||
</div>
|
||||
|
||||
<input id="username" 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 />
|
||||
<input
|
||||
id="username"
|
||||
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' }}
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -51,7 +96,7 @@ const hasOAuth = ref(false)
|
|||
const hasLocalLogin = ref(false)
|
||||
const oauthProviders = ref([])
|
||||
|
||||
function loadLoginOptions() {
|
||||
function loadLoginOptions () {
|
||||
// Use the init response data that was loaded in App.vue
|
||||
if (window.initResponse) {
|
||||
hasOAuth.value = window.initResponse.oAuth2Providers && window.initResponse.oAuth2Providers.length > 0
|
||||
|
|
@ -65,7 +110,7 @@ function loadLoginOptions() {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleLocalLogin() {
|
||||
async function handleLocalLogin () {
|
||||
loading.value = true
|
||||
loginError.value = ''
|
||||
|
||||
|
|
@ -105,7 +150,7 @@ async function handleLocalLogin() {
|
|||
}
|
||||
}
|
||||
|
||||
function loginWithOAuth(provider) {
|
||||
function loginWithOAuth (provider) {
|
||||
if (!provider.key) {
|
||||
console.error('OAuth provider missing key:', provider)
|
||||
return
|
||||
|
|
@ -119,7 +164,7 @@ onMounted(() => {
|
|||
loadLoginOptions()
|
||||
|
||||
// Also watch for when init response becomes available
|
||||
const stopWatcher = watch(() => window.initResponse, () => {
|
||||
watch(() => window.initResponse, () => {
|
||||
loadLoginOptions()
|
||||
}, { immediate: true })
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,23 @@
|
|||
<template>
|
||||
<Section :title="t('logs.calendar-title')" :padding="false">
|
||||
<Section
|
||||
:title="t('logs.calendar-title')"
|
||||
:padding="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<router-link to="/logs" 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"/>
|
||||
<router-link
|
||||
to="/logs"
|
||||
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>
|
||||
{{ t('logs.back-to-list') }}
|
||||
</router-link>
|
||||
|
|
@ -62,8 +76,8 @@ const calendarEvents = computed(() => {
|
|||
id: log.executionTrackingId,
|
||||
title: log.actionTitle || 'Untitled Action',
|
||||
date: startDate,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
startDate,
|
||||
endDate,
|
||||
actionIcon: log.actionIcon,
|
||||
user: log.user,
|
||||
tags: log.tags,
|
||||
|
|
@ -72,7 +86,7 @@ const calendarEvents = computed(() => {
|
|||
})
|
||||
})
|
||||
|
||||
async function fetchLogs() {
|
||||
async function fetchLogs () {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
|
|
@ -82,11 +96,10 @@ async function fetchLogs() {
|
|||
const allLogs = []
|
||||
let startOffset = BigInt(0)
|
||||
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) {
|
||||
const args = {
|
||||
"startOffset": startOffset,
|
||||
startOffset
|
||||
}
|
||||
|
||||
const response = await window.client.getLogs(args)
|
||||
|
|
@ -120,14 +133,14 @@ async function fetchLogs() {
|
|||
}
|
||||
}
|
||||
|
||||
function handleEventClick(event) {
|
||||
function handleEventClick (event) {
|
||||
// Navigate to the execution view when clicking on a calendar event
|
||||
if (event.id) {
|
||||
router.push(`/logs/${event.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDayClick(date) {
|
||||
function handleDayClick (date) {
|
||||
// Navigate to logs list filtered by the selected date
|
||||
// Format date as YYYY-MM-DD using local date components to avoid timezone issues
|
||||
const year = date.getFullYear()
|
||||
|
|
@ -137,7 +150,7 @@ function handleDayClick(date) {
|
|||
router.push({ path: '/logs', query: { date: dateString } })
|
||||
}
|
||||
|
||||
function handleMonthChange(month, year) {
|
||||
function handleMonthChange (month, year) {
|
||||
currentMonthIndex.value = month
|
||||
currentYear.value = year
|
||||
// Optionally fetch logs for the new month if needed
|
||||
|
|
|
|||
|
|
@ -1,30 +1,61 @@
|
|||
<template>
|
||||
<Section :title="t('logs.title')" :padding="false">
|
||||
<Section
|
||||
:title="t('logs.title')"
|
||||
:padding="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<router-link to="/logs/queue" class="button neutral">
|
||||
<router-link
|
||||
to="/logs/queue"
|
||||
class="button neutral"
|
||||
>
|
||||
{{ t('logs.queue') }}
|
||||
</router-link>
|
||||
<router-link to="/logs/calendar" class="button neutral">
|
||||
<router-link
|
||||
to="/logs/calendar"
|
||||
class="button neutral"
|
||||
>
|
||||
{{ t('logs.calendar') }}
|
||||
</router-link>
|
||||
<label class="input-with-icons">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" 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
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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>
|
||||
<input
|
||||
:placeholder="t('logs.filter-placeholder')"
|
||||
v-model="searchText"
|
||||
:placeholder="t('logs.filter-placeholder')"
|
||||
list="logs-filter-suggestions"
|
||||
:aria-invalid="filterError ? 'true' : 'false'"
|
||||
/>
|
||||
>
|
||||
<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>
|
||||
<button :title="t('logs.clear-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" />
|
||||
<button
|
||||
:title="t('logs.clear-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>
|
||||
</button>
|
||||
</label>
|
||||
|
|
@ -38,7 +69,13 @@
|
|||
<p>{{ t('logs.filter-help-fields') }}</p>
|
||||
<p><code>{{ t('logs.filter-help-examples') }}</code></p>
|
||||
</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 v-show="logs.length > 0">
|
||||
|
|
@ -48,12 +85,26 @@
|
|||
<th>
|
||||
<div class="timestamp-header">
|
||||
<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>
|
||||
<button :title="t('logs.clear-date-filter')" @click="clearDateFilter" class="clear-date-button">
|
||||
<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" />
|
||||
<button
|
||||
:title="t('logs.clear-date-filter')"
|
||||
class="clear-date-button"
|
||||
@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>
|
||||
</button>
|
||||
</span>
|
||||
|
|
@ -65,12 +116,25 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="log in logs" :key="log.executionTrackingId" class="log-row" :title="log.actionTitle">
|
||||
<td class="timestamp">{{ formatTimestamp(log.datetimeStarted) }}</td>
|
||||
<tr
|
||||
v-for="log in logs"
|
||||
:key="log.executionTrackingId"
|
||||
class="log-row"
|
||||
:title="log.actionTitle"
|
||||
>
|
||||
<td class="timestamp">
|
||||
{{ formatTimestamp(log.datetimeStarted) }}
|
||||
</td>
|
||||
<td>
|
||||
<ActionIconGlyph class="icon" :glyph="log.actionIcon" />
|
||||
<ActionIconGlyph
|
||||
class="icon"
|
||||
:glyph="log.actionIcon"
|
||||
/>
|
||||
<router-link :to="`/logs/${log.executionTrackingId}`">
|
||||
<LogActionTitle :action-title="log.actionTitle" :justification="log.justification" />
|
||||
<LogActionTitle
|
||||
:action-title="log.actionTitle"
|
||||
:justification="log.justification"
|
||||
/>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="tags">
|
||||
|
|
@ -78,38 +142,70 @@
|
|||
<span class="annotation-key">User:</span>
|
||||
<span class="annotation-val">{{ log.user }}</span>
|
||||
</span>
|
||||
<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
|
||||
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>
|
||||
</td>
|
||||
<td class="exit-code">
|
||||
<ActionStatusDisplay :logEntry="log" />
|
||||
<ActionStatusDisplay :log-entry="log" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class = "padding"
|
||||
@page-size-change="handlePageSizeChange" itemTitle="execution logs" />
|
||||
<Pagination
|
||||
: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 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>
|
||||
<button @click="clearSearch" class="button neutral">
|
||||
<button
|
||||
class="button neutral"
|
||||
@click="clearSearch"
|
||||
>
|
||||
{{ t('logs.clear-filter') }}
|
||||
</button>
|
||||
</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>
|
||||
<button @click="clearDateFilter" class="button neutral">
|
||||
<button
|
||||
class="button neutral"
|
||||
@click="clearDateFilter"
|
||||
>
|
||||
{{ t('logs.clear-date-filter') }}
|
||||
</button>
|
||||
</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>
|
||||
<router-link to="/">{{ t('return-to-index') }}</router-link>
|
||||
<router-link to="/">
|
||||
{{ t('return-to-index') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
|
|
@ -150,7 +246,7 @@ const filterSuggestions = [
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
function updateDateFromRoute() {
|
||||
function updateDateFromRoute () {
|
||||
const dateParam = route.query.date
|
||||
if (dateParam) {
|
||||
selectedDate.value = dateParam
|
||||
|
|
@ -169,7 +265,7 @@ watch(searchText, () => {
|
|||
scheduleFetchLogs()
|
||||
})
|
||||
|
||||
async function fetchLogs() {
|
||||
async function fetchLogs () {
|
||||
loading.value = true
|
||||
filterError.value = ''
|
||||
try {
|
||||
|
|
@ -206,7 +302,7 @@ async function fetchLogs() {
|
|||
}
|
||||
}
|
||||
|
||||
function scheduleFetchLogs() {
|
||||
function scheduleFetchLogs () {
|
||||
if (fetchTimer) {
|
||||
clearTimeout(fetchTimer)
|
||||
}
|
||||
|
|
@ -215,18 +311,18 @@ function scheduleFetchLogs() {
|
|||
}, 400)
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
function clearSearch () {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
function clearDateFilter() {
|
||||
function clearDateFilter () {
|
||||
selectedDate.value = null
|
||||
const query = { ...route.query }
|
||||
delete query.date
|
||||
router.push({ path: route.path, query })
|
||||
}
|
||||
|
||||
function formatDateFilter(dateString) {
|
||||
function formatDateFilter (dateString) {
|
||||
try {
|
||||
const date = new Date(dateString + 'T00:00:00')
|
||||
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
|
||||
|
|
@ -235,7 +331,7 @@ function formatDateFilter(dateString) {
|
|||
}
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
function formatTimestamp (timestamp) {
|
||||
if (!timestamp) return 'Unknown'
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
|
|
@ -245,18 +341,18 @@ function formatTimestamp(timestamp) {
|
|||
}
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
function handlePageChange (page) {
|
||||
currentPage.value = page
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(newPageSize) {
|
||||
function handlePageSizeChange (newPageSize) {
|
||||
pageSize.value = newPageSize
|
||||
currentPage.value = 1
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
function onExecutionEvent(evt) {
|
||||
function onExecutionEvent (evt) {
|
||||
const logEntry = getExecutionLogEntry(evt)
|
||||
if (!logEntry) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,19 +1,40 @@
|
|||
<template>
|
||||
<Section :title="t('logs.queue-title')" :padding="false">
|
||||
<Section
|
||||
:title="t('logs.queue-title')"
|
||||
:padding="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<router-link to="/logs" 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"/>
|
||||
<router-link
|
||||
to="/logs"
|
||||
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>
|
||||
{{ t('logs.back-to-list') }}
|
||||
</router-link>
|
||||
</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>
|
||||
<router-link to="/logs">{{ t('logs.back-to-list') }}</router-link>
|
||||
<router-link to="/logs">
|
||||
{{ t('logs.back-to-list') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
|
@ -24,7 +45,10 @@
|
|||
>
|
||||
<div class="section-header flex-row">
|
||||
<div class="fg1 queue-action-group-heading">
|
||||
<ActionIconGlyph class="icon" :glyph="actionGroup.icon" />
|
||||
<ActionIconGlyph
|
||||
class="icon"
|
||||
:glyph="actionGroup.icon"
|
||||
/>
|
||||
<h2>{{ displayActionGroupName(actionGroup.name) }}</h2>
|
||||
</div>
|
||||
<ActionGroupLimitsLabel
|
||||
|
|
@ -44,23 +68,34 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<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
|
||||
v-for="(entry, index) in action.entries"
|
||||
:key="entry.executionTrackingId"
|
||||
class="log-row"
|
||||
:title="action.actionTitle"
|
||||
>
|
||||
<td class="timestamp">{{ formatTimestamp(entry.datetimeStarted) }}</td>
|
||||
<td class="timestamp">
|
||||
{{ formatTimestamp(entry.datetimeStarted) }}
|
||||
</td>
|
||||
<td>
|
||||
<ActionIconGlyph class="icon" :glyph="action.actionIcon" />
|
||||
<ActionIconGlyph
|
||||
class="icon"
|
||||
:glyph="action.actionIcon"
|
||||
/>
|
||||
<router-link :to="`/logs/${entry.executionTrackingId}`">
|
||||
<LogActionTitle
|
||||
:action-title="action.actionTitle"
|
||||
:justification="entry.justification"
|
||||
/>
|
||||
</router-link>
|
||||
<span v-if="action.entityPrefix" class="queue-entity annotation">
|
||||
<span
|
||||
v-if="action.entityPrefix"
|
||||
class="queue-entity annotation"
|
||||
>
|
||||
{{ action.entityPrefix }}
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -69,15 +104,28 @@
|
|||
<span class="annotation-key">User:</span>
|
||||
<span class="annotation-val">{{ entry.user }}</span>
|
||||
</span>
|
||||
<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
|
||||
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>
|
||||
</td>
|
||||
<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 }) }}
|
||||
</span>
|
||||
<ActionStatusDisplay :logEntry="entry" link-queued-status />
|
||||
<ActionStatusDisplay
|
||||
:log-entry="entry"
|
||||
link-queued-status
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@
|
|||
<h2>Page Not Found</h2>
|
||||
|
||||
<div class="actions">
|
||||
<button class = "button good" @click="goToHome">
|
||||
<button
|
||||
class="button good"
|
||||
@click="goToHome"
|
||||
>
|
||||
Go to Home
|
||||
</button>
|
||||
<button class="button neutral" @click="goBack">
|
||||
<button
|
||||
class="button neutral"
|
||||
@click="goBack"
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -22,10 +28,10 @@
|
|||
export default {
|
||||
name: 'NotFoundView',
|
||||
methods: {
|
||||
goBack() {
|
||||
goBack () {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
goToHome() {
|
||||
goToHome () {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,58 @@
|
|||
<template>
|
||||
<Section title="User Information" class="small">
|
||||
<div v-if="!isLoggedIn" class="user-not-logged-in">
|
||||
<Section
|
||||
title="User Information"
|
||||
class="small"
|
||||
>
|
||||
<div
|
||||
v-if="!isLoggedIn"
|
||||
class="user-not-logged-in"
|
||||
>
|
||||
<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 v-else class="user-control-panel">
|
||||
<div
|
||||
v-else
|
||||
class="user-control-panel"
|
||||
>
|
||||
<dl class="user-info">
|
||||
<dt>Username</dt>
|
||||
<dd>{{ username }}</dd>
|
||||
<dt v-if="userProvider !== 'system'">Provider</dt>
|
||||
<dd v-if="userProvider !== 'system'">{{ userProvider }}</dd>
|
||||
<dt v-if="usergroup">Group</dt>
|
||||
<dd v-if="usergroup">{{ usergroup }}</dd>
|
||||
<dt v-if="acls && acls.length > 0">Matched ACLs</dt>
|
||||
<dt v-if="userProvider !== 'system'">
|
||||
Provider
|
||||
</dt>
|
||||
<dd v-if="userProvider !== 'system'">
|
||||
{{ 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">
|
||||
<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>
|
||||
</dl>
|
||||
|
||||
<div class="user-actions">
|
||||
<div class="action-buttons">
|
||||
<button @click="handleLogout" class="button bad" :disabled="loggingOut">
|
||||
<button
|
||||
class="button bad"
|
||||
:disabled="loggingOut"
|
||||
@click="handleLogout"
|
||||
>
|
||||
{{ loggingOut ? 'Logging out...' : 'Logout' }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -31,7 +62,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
|
||||
|
|
@ -44,7 +75,7 @@ const usergroup = ref('')
|
|||
const loggingOut = ref(false)
|
||||
const acls = ref([])
|
||||
|
||||
function updateUserInfo() {
|
||||
function updateUserInfo () {
|
||||
if (window.initResponse) {
|
||||
isLoggedIn.value = window.initResponse.authenticatedUser !== '' && window.initResponse.authenticatedUser !== 'guest'
|
||||
username.value = window.initResponse.authenticatedUser
|
||||
|
|
@ -53,7 +84,7 @@ function updateUserInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchWhoAmI() {
|
||||
async function fetchWhoAmI () {
|
||||
try {
|
||||
const res = await window.client.whoAmI({})
|
||||
acls.value = res.acls || []
|
||||
|
|
@ -67,7 +98,7 @@ async function fetchWhoAmI() {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
async function handleLogout () {
|
||||
loggingOut.value = true
|
||||
|
||||
try {
|
||||
|
|
@ -102,13 +133,12 @@ async function handleLogout() {
|
|||
}
|
||||
}
|
||||
|
||||
let watchInterval = null
|
||||
const watchInterval = null
|
||||
|
||||
onMounted(() => {
|
||||
updateUserInfo()
|
||||
fetchWhoAmI()
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (watchInterval) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"chai": "^6.2.2",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint": "^10.6.0",
|
||||
"mocha": "^11.7.6",
|
||||
"selenium-webdriver": "^4.45.0"
|
||||
}
|
||||
|
|
@ -783,9 +783,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz",
|
||||
"integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==",
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
|
||||
"integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"license": "AGPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"chai": "^6.2.2",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint": "^10.6.0",
|
||||
"mocha": "^11.7.6",
|
||||
"selenium-webdriver": "^4.45.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
module github.com/OliveTin/OliveTin/langtool
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
|||
20
lang/go.sum
20
lang/go.sum
|
|
@ -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/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-20250919212919-976d085a100c/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/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da h1:hYsJqujd3A4Xtp9swe2d6Y6ij2ecd16E+i2oHAH/xaA=
|
||||
github.com/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da/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/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/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/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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ TOOL_GROUPS: list[tuple[str, frozenset[str]]] = [
|
|||
),
|
||||
(
|
||||
"Go codestyle (install via: make go-tools)",
|
||||
frozenset({"gocyclo", "gocritic"}),
|
||||
frozenset({"golangci-lint"}),
|
||||
),
|
||||
(
|
||||
"Protocol buffers (install via: make -C service go-tools-all)",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -30,8 +30,7 @@ compile: compile-armhf compile-x64-lin compile-x64-win
|
|||
codestyle: go-tools
|
||||
go fmt ./...
|
||||
go vet ./...
|
||||
gocyclo -over 4 internal
|
||||
gocritic check ./...
|
||||
golangci-lint run ./...
|
||||
|
||||
test: unittests
|
||||
|
||||
|
|
@ -54,13 +53,11 @@ find-flakey-tests-inf:
|
|||
go run ./scripts/find-flakey-tests-inf
|
||||
|
||||
go-tools:
|
||||
go install "github.com/fzipp/gocyclo/cmd/gocyclo"
|
||||
go install "github.com/go-critic/go-critic/cmd/gocritic"
|
||||
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
|
||||
|
||||
.PHONY: unittests unittests-fast find-flakey-tests find-flakey-tests-inf
|
||||
|
||||
go-tools-all:
|
||||
go install "github.com/bufbuild/buf/cmd/buf"
|
||||
go install "github.com/fzipp/gocyclo/cmd/gocyclo"
|
||||
go install "github.com/go-critic/go-critic/cmd/gocritic"
|
||||
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
|
||||
go install "google.golang.org/protobuf/cmd/protoc-gen-go"
|
||||
|
|
|
|||
272
service/go.mod
272
service/go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/OliveTin/OliveTin
|
||||
|
||||
go 1.25.10
|
||||
go 1.26
|
||||
|
||||
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/PaesslerAG/jsonpath v0.1.1
|
||||
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/fzipp/gocyclo v0.6.0
|
||||
github.com/go-critic/go-critic v0.14.3
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
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/jamesread/golure v0.0.0-20260510214136-6ef80e0ce8da
|
||||
github.com/knadh/koanf/parsers/yaml 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/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/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
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/sys v0.45.0
|
||||
golang.org/x/sys v0.47.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
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/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/protocolbuffers/go v1.36.11-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-20260629211719-be09125d9193.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/bufplugin v0.10.0 // indirect
|
||||
|
|
@ -50,33 +53,86 @@ require (
|
|||
buf.build/go/spdx v0.2.0 // indirect
|
||||
buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 // 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
|
||||
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/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/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/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/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/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/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/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/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/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/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v29.5.2+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v28.5.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.7 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/docker/cli v29.6.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.8 // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/expr-lang/expr v1.17.8 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // 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/stdr v1.2.2 // 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/astfmt 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/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/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-containerregistry v0.21.6 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/google/go-containerregistry v0.21.7 // 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/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/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-runewidth v0.0.23 // indirect
|
||||
github.com/mgechev/revive v1.15.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.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/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.1 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/morikuni/aec v1.1.0 // indirect
|
||||
github.com/moby/moby/api v1.55.0 // indirect
|
||||
github.com/moby/moby/client v0.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // 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/image-spec v1.1.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // 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/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/prometheus/common v0.69.0 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // 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/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // 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/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.4.1 // 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/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/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
github.com/spf13/viper v1.12.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/vbatts/tar-split v0.12.3 // indirect
|
||||
go.lsp.dev/jsonrpc2 v0.10.0 // indirect
|
||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
|
||||
go.lsp.dev/protocol v0.12.0 // indirect
|
||||
go.lsp.dev/uri v0.3.0 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect
|
||||
github.com/timonwong/loggercheck v0.11.0 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // 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/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.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
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20260508232706-74f9aab9d74a // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20260611194520-c48552f49976 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
golang.org/x/tools v0.47.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // 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
|
||||
pluginrpc.com/pluginrpc v0.5.0 // indirect
|
||||
)
|
||||
|
|
|
|||
979
service/go.sum
979
service/go.sum
File diff suppressed because it is too large
Load Diff
|
|
@ -134,7 +134,7 @@ func IsAllowedKill(cfg *config.Config, user *authpublic.AuthenticatedUser, actio
|
|||
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 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
|
||||
|
||||
for _, acl := range cfg.AccessControlLists {
|
||||
if isACLRelevantToAction(cfg, actionAcls, acl, user) {
|
||||
if isACLRelevantToAction(actionAcls, acl, user) {
|
||||
ret = append(ret, acl)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func createKeys(t *testing.T) (*rsa.PrivateKey, string) {
|
|||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
defer func() { _ = tmpFile.Close() }()
|
||||
|
||||
t.Logf("Created File: %s", tmpFile.Name())
|
||||
|
||||
|
|
@ -65,10 +65,6 @@ func newMux() *http.ServeMux {
|
|||
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 {
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
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) {
|
||||
defer res.Body.Close()
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
assert.Equal(t, expectCode, res.StatusCode)
|
||||
body, _ := io.ReadAll(res.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) {
|
||||
privateKey, publicKeyPath := createKeys(t)
|
||||
defer os.Remove(publicKeyPath)
|
||||
defer func() { _ = os.Remove(publicKeyPath) }()
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.AuthJwtPubKeyPath = publicKeyPath
|
||||
|
|
@ -186,7 +182,7 @@ func makeJWTRequest(t *testing.T, srv *httptest.Server, tokenStr string) *http.R
|
|||
|
||||
func TestJWTHeader(t *testing.T) {
|
||||
privateKey, publicKeyPath := createKeys(t)
|
||||
defer os.Remove(publicKeyPath)
|
||||
defer func() { _ = os.Remove(publicKeyPath) }()
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.AuthJwtPubKeyPath = publicKeyPath
|
||||
|
|
@ -217,7 +213,7 @@ func TestJWTHeader(t *testing.T) {
|
|||
defer srv.Close()
|
||||
|
||||
res := makeJWTRequest(t, srv, tokenStr)
|
||||
defer res.Body.Close()
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ func getUserInfo(cfg *config.Config, client *http.Client, provider *config.OAuth
|
|||
return ret
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(contents), &userData)
|
||||
err = json.Unmarshal(contents, &userData)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to unmarshal user data: %v", err)
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ actions:
|
|||
// Create temporary file
|
||||
tmpFile, err := os.CreateTemp("", "test_config_*.yaml")
|
||||
assert.NoError(t, err, "Should create temporary file")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
||||
|
||||
// Write test config to file
|
||||
_, err = tmpFile.WriteString(testConfig)
|
||||
assert.NoError(t, err, "Should write test config to file")
|
||||
tmpFile.Close()
|
||||
assert.NoError(t, tmpFile.Close())
|
||||
|
||||
// Load config using koanf
|
||||
k := koanf.New(".")
|
||||
|
|
@ -82,11 +82,11 @@ actions:
|
|||
|
||||
tmpFile, err := os.CreateTemp("", "test_config_empty_*.yaml")
|
||||
assert.NoError(t, err, "Should create temporary file")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
||||
|
||||
_, err = tmpFile.WriteString(testConfig)
|
||||
assert.NoError(t, err, "Should write test config to file")
|
||||
tmpFile.Close()
|
||||
assert.NoError(t, tmpFile.Close())
|
||||
|
||||
k := koanf.New(".")
|
||||
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
|
||||
|
|
@ -117,11 +117,11 @@ actions:
|
|||
|
||||
tmpFile, err := os.CreateTemp("", "test_config_disabled_*.yaml")
|
||||
assert.NoError(t, err, "Should create temporary file")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
||||
|
||||
_, err = tmpFile.WriteString(testConfig)
|
||||
assert.NoError(t, err, "Should write test config to file")
|
||||
tmpFile.Close()
|
||||
assert.NoError(t, tmpFile.Close())
|
||||
|
||||
k := koanf.New(".")
|
||||
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
|
||||
|
|
@ -148,11 +148,11 @@ actions:
|
|||
|
||||
tmpFile, err := os.CreateTemp("", "test_config_no_auth_*.yaml")
|
||||
assert.NoError(t, err, "Should create temporary file")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
||||
|
||||
_, err = tmpFile.WriteString(testConfig)
|
||||
assert.NoError(t, err, "Should write test config to file")
|
||||
tmpFile.Close()
|
||||
assert.NoError(t, tmpFile.Close())
|
||||
|
||||
k := koanf.New(".")
|
||||
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
|
||||
|
|
|
|||
|
|
@ -982,7 +982,7 @@ func validatedSystemArgs(req *ExecutionRequest) (map[string]string, error) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -186,16 +186,6 @@ func (e *Executor) groupsHaveCapacityForQueued(req *ExecutionRequest) bool {
|
|||
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 {
|
||||
for _, limit := range actionGroupLimits(req) {
|
||||
if e.countActiveInGroupLocked(limit.name) >= (limit.maxConcurrent + 1) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (tc *timeoutContext) setProcess(process *os.Process) {
|
|||
tc.processMu.Unlock()
|
||||
|
||||
// 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}
|
||||
if err := tc.executor.Kill(logEntry); err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func watchPath(ctx *watchContext) {
|
|||
return
|
||||
}
|
||||
|
||||
defer watcher.Close()
|
||||
defer func() { _ = watcher.Close() }()
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ func WriteFile(filename string, out []byte) {
|
|||
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
handle, err := os.Create(filename)
|
||||
handle.Close()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
|
|
@ -25,6 +23,7 @@ func WriteFile(filename string, out []byte) {
|
|||
return
|
||||
}
|
||||
|
||||
_ = handle.Close()
|
||||
}
|
||||
|
||||
err := os.WriteFile(filename, out, 0600)
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func getOsReleasePrettyName() string {
|
|||
}
|
||||
}
|
||||
|
||||
handle.Close()
|
||||
_ = handle.Close()
|
||||
|
||||
return "notfound"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package servicehost
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
@ -20,25 +19,3 @@ func resolveLogDirectory(dir string, baseDir string) string {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ func appendFile(path, content string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
_, err = file.WriteString(content)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ package tools
|
|||
|
||||
import (
|
||||
_ "github.com/bufbuild/buf/cmd/buf"
|
||||
_ "github.com/fzipp/gocyclo/cmd/gocyclo"
|
||||
_ "github.com/go-critic/go-critic/cmd/gocritic"
|
||||
_ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
|
||||
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue