Compare commits

...

638 Commits

Author SHA1 Message Date
James Read
d4b8743a57 Next (#772)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-12-01 02:08:58 +00:00
jamesread
39009fcdd1 chore: code fmt
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-12-01 01:59:10 +00:00
jamesread
56365af24b feat: Mega entity improvements - entity directories, inehrit entities (#450), ordering (#762 / #703), better entity view 2025-12-01 01:38:04 +00:00
jamesread
736ad1c83b fix: Dashboard directories and displays modest style fixes 2025-12-01 00:38:02 +00:00
jamesread
fe8ecf49eb Merge branch 'next' of github.com:OliveTin/OliveTin into next 2025-12-01 00:24:13 +00:00
James Read
7282d7a9f6 Feat (#763) mre output in 3k (#771) 2025-12-01 00:24:03 +00:00
jamesread
a163347992 feat: Sidebar now closes when clicking outside of it (#714) 2025-12-01 00:23:03 +00:00
jamesread
a9b21ea645 feat: Display a big error message if OliveTin initialization fails 2025-12-01 00:22:18 +00:00
jamesread
c59bcc62db chore: fix sleep action test 2025-12-01 00:15:01 +00:00
James Read
ef072806da chore(deps-dev): bump glob from 10.4.5 to 10.5.0 in /integration-tests (#767) 2025-12-01 00:06:53 +00:00
James Read
f15f0ef329 feat: action status display on logs list (#770) 2025-12-01 00:06:20 +00:00
jamesread
caaf37e091 feat: Most Recent Execution output in dashboard 2025-12-01 00:02:42 +00:00
jamesread
abecf6f8c6 feat: Action status display on logs list 2025-11-30 22:41:06 +00:00
James Read
6bc3a7bf1f Merge branch 'next' into dependabot/npm_and_yarn/integration-tests/glob-10.5.0
Some checks failed
Codestyle checks / codestyle (push) Has been cancelled
2025-11-30 16:55:16 +00:00
jamesread
7425db53f6 fix: #718 - Clear OAuth2 authentication cookie on logout 2025-11-30 11:19:36 +00:00
James Read
09d933722e fix: #765 Page title was not being set (#769)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-30 10:29:07 +00:00
jamesread
6ccd10a970 feat: Added copy to clipboard buttons to Diagnostics view, with markdown backticks 2025-11-30 10:28:07 +00:00
jamesread
dca8e04518 fix: #765 Page title was not being set 2025-11-29 21:16:23 +00:00
jamesread
a91e903873 feat: add browser info to diagnostics
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-29 20:37:08 +00:00
dependabot[bot]
4686b044e7 chore(deps-dev): bump glob from 10.4.5 to 10.5.0 in /integration-tests
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 01:10:24 +00:00
James Read
f28944bee2 Next (#766)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-11-27 01:09:06 +00:00
jamesread
470f31db6e chore: Remove unecessary OAauth2 log
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-27 00:41:53 +00:00
jamesread
0046ae427d chore: fix broken integration test for fieldset markup 2025-11-27 00:37:15 +00:00
jamesread
8bad1b5400 fix: #703 - Entities order was non-deterministic 2025-11-27 00:02:08 +00:00
jamesread
6b4dfddf4c Merge branch 'next' of github.com:OliveTin/OliveTin into next 2025-11-26 23:30:13 +00:00
James Read
ff77e9121c fix: (#718) OAuth Login Buttons not redirecting (#759) 2025-11-26 23:28:57 +00:00
jamesread
7f1e509e12 chore: Little fixes for OAuth2 login 2025-11-26 23:28:29 +00:00
jamesread
f33ccbd2fa fix: Log errors when loading theme CSS 2025-11-26 23:08:10 +00:00
jamesread
e9bde8b094 fix: OAuth2 scopes for GitHub 2025-11-26 23:07:55 +00:00
jamesread
853972384c security: Possible to kill actions without authentication & credential leak in logs 2025-11-26 23:07:25 +00:00
jamesread
695ad65d3e chore: Improve dashboard markup and layout 2025-11-26 22:57:26 +00:00
jamesread
b47cf7cab3 fix: Fix dashboard legends being left-aligned to centered again 2025-11-26 22:38:45 +00:00
jamesread
be7c754043 feat: Rebuild authentication system to support 3k authentication 2025-11-26 22:33:19 +00:00
jamesread
b244b42e91 fix: sort OAuth2 providers by key 2025-11-22 23:27:54 +00:00
jamesread
3d763a84df fix: Various oauth issues 2025-11-22 22:39:05 +00:00
jamesread
d05ea54f8d fix: (#718) OAuth Login Buttons not redirecting 2025-11-22 21:13:19 +00:00
jamesread
55d8f75d1b fix: Sidebar, when unstuck, clicking links will close it (#714)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-22 20:44:44 +00:00
jamesread
c663c9d10d chore: Update release notes to strip out nonsense 2025-11-22 20:06:07 +00:00
James Read
7f12b6d72c chore: colocate tests (#758) 2025-11-22 19:57:26 +00:00
jamesread
3cbafcb4fc chore: Remove dummy link from config.yaml 2025-11-22 19:49:53 +00:00
jamesread
458f50a6ae chore: colocate tests 2025-11-22 19:24:51 +00:00
James Read
1b89b3e217 doc: Add config tool to help support people (#713)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-22 10:59:12 +00:00
jamesread
3e6a751132 chore: config-tool wont overwrite original config 2025-11-22 10:49:48 +00:00
jamesread
49b8c2c4f2 chore: fix broken datetime test 2025-11-22 10:43:56 +00:00
James Read
738ec884d8 docs(contributing): update setup instructions for pre-commit (#717) 2025-11-22 09:50:55 +00:00
James Read
d2e7474eea Merge branch 'next' into chore-add-config-tool 2025-11-22 09:47:14 +00:00
jamesread
ca0a134acd chore: write back to the same config path in config-tool 2025-11-22 09:34:37 +00:00
jamesread
40d9377bbd fmt: main.js 2025-11-22 08:36:58 +00:00
jamesread
5bf2d6935b chore: dep update 2025-11-22 08:36:38 +00:00
jamesread
15390f7b80 fix: #721 Datetime args not being rendered 2025-11-22 08:34:53 +00:00
jamesread
d4c80c65c2 chore: Configure dependabot for 2 release branches
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-11-22 08:00:52 +00:00
Anton Bobov
378db80c73 docs(contributing): update setup instructions for pre-commit
Update CONTRIBUTING guide to reflect migration from custom git hooks to
standard pre-commit framework:

* Add pre-commit package to installation commands for Fedora and Windows
* Replace 'make githooks' with standard 'pre-commit install' command

This completes the migration to pre-commit started in previous refactor
commit, ensuring documentation matches current development workflow.
2025-11-20 00:08:10 +05:00
jamesread
b3e67bad75 doc: Add config tool to help support people 2025-11-16 23:08:20 +00:00
James Read
c2843aa581 3k release: terminal sizes, confirmation buttons, translations, entity fieldsets drawn in random order, code quality (#709)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-11-16 00:51:51 +00:00
jamesread
d1ec688c9a chore: cleanup executor.go
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-16 00:47:03 +00:00
jamesread
c1508a0a65 fix: Replace custom pagination component with picocrank pagination component
- Replace custom pagination component with picocrank pagination component
- Update package.json and package-lock.json to include picocrank
- Update style.css to include new padding class
- Update ActionDetailsView.vue and LogsListView.vue to use new pagination component
2025-11-16 00:05:50 +00:00
jamesread
5b57cf2480 fix: Logs were being displayed in the wrong order 2025-11-11 23:32:00 +00:00
jamesread
b97dd23abb chore: Better fixups on lang tool and support 2025-11-11 23:24:44 +00:00
jamesread
7c66170ef5 Merge branch 'next' of github.com:OliveTin/OliveTin into next
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-11 10:55:54 +00:00
jamesread
167e700e30 chore: cleanup websocket code 2025-11-11 10:55:42 +00:00
James Read
1f875b05a0 Merge branch 'main' into next 2025-11-11 10:55:24 +00:00
jamesread
00d7285167 chore: fix some linting issues 2025-11-11 10:43:47 +00:00
jamesread
d32d92baab Merge branch 'next' of github.com:OliveTin/OliveTin into next 2025-11-11 10:25:00 +00:00
jamesread
36c786a26d chore: Cleanup websocket/marshaller code 2025-11-11 10:20:54 +00:00
jamesread
afbd1c3abe dep updates 2025-11-11 00:30:34 +00:00
James Read
09ecc8d15c feat: Add translations and language support (#708)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-11 00:26:03 +00:00
jamesread
487bf83f4e chore: use koanf tag in config reloader test 2025-11-10 23:39:15 +00:00
jamesread
adae55f24b chore: translate logs list view, log statuses, and language dialog 2025-11-10 23:33:55 +00:00
jamesread
217b17a058 chore: (#708) Print key validation errors, translate the language dialog 2025-11-10 23:26:55 +00:00
jamesread
67a9d3b1d1 chore: skip env in config test in 3k 2025-11-10 23:17:53 +00:00
jamesread
de7129e1d7 chore: reduce cyclomatic complexity 2025-11-10 23:15:18 +00:00
jamesread
3df22f2ccb Merge branch 'feat-add-language-support' of github.com:OliveTin/OliveTin into feat-add-language-support 2025-11-10 22:50:58 +00:00
jamesread
1140453f30 fix: broken integration test after fieldsets changes 2025-11-10 22:50:42 +00:00
James Read
9cf79863b3 Merge branch 'next' into feat-add-language-support 2025-11-10 21:53:04 +00:00
jamesread
6a853d9c99 feat: (#708) Added language support 2025-11-10 21:52:36 +00:00
jamesread
83fe489949 fix: (#703) Fixed entity fieldsets being drawn in a random order 2025-11-10 21:08:34 +00:00
jamesread
e7d4747727 chore: fix broken integration test after fieldsets changes 2025-11-10 20:52:49 +00:00
James Read
d4d7bf8135 fix: (#704) Confirmation buttons (#707) 2025-11-10 20:46:48 +00:00
jamesread
32a062ae93 fix: (#707) Confirmation buttons and ASCII input types 2025-11-10 20:12:19 +00:00
jamesread
263601170f fix: (#702) Fixed actions being drawn in a column (an extra fieldset was being added) 2025-11-10 19:46:58 +00:00
jamesread
b071b4d036 feat: Increase terminal size, and in fullscreen mode 2025-11-10 19:46:08 +00:00
jamesread
52ff504a9d fix: (#704) Confirmation buttons 2025-11-10 13:21:54 +00:00
jamesread
2ed564a403 feat: Add translations and language support 2025-11-10 13:20:40 +00:00
James Read
209856eda9 3k release: massively improve config loading, reduce log spam, etc (#700)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-11-07 00:55:08 +00:00
jamesread
3967b91cf0 fix: Exit if no base config file is found
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-07 00:51:04 +00:00
James Read
50abb53ace Fix config loading missing values (#699) 2025-11-07 00:40:36 +00:00
James Read
110bbd6216 Merge branch 'next' into fix-config-loading-missing-values 2025-11-07 00:34:11 +00:00
jamesread
1552c104e9 fix: Propperly merge included configs (handling actions) 2025-11-07 00:31:33 +00:00
James Read
4006fd485d fix: Various log noise and spam (#698)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-11-06 23:48:11 +00:00
jamesread
7dc99b1398 fix: Massively improve config loading 2025-11-06 23:45:54 +00:00
jamesread
581536a60f fmt: GetActionLogs 2025-11-06 23:44:56 +00:00
jamesread
58593c6f04 doc: Improve HAProxy example 2025-11-06 23:44:00 +00:00
jamesread
39664a734d fix: Various log noise 2025-11-06 23:42:07 +00:00
James Read
87f9a0b152 fix: #696 - hide login link if login interactively isn't possible (local or oauth2) (#697)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-06 15:44:52 +00:00
jamesread
d688ab64e1 fix: #696 - hide login link if login interactively isn't possible (local user or oauth2) 2025-11-06 15:29:39 +00:00
jamesread
822f3197b6 chore: (#675) Remove dead websocket code 2025-11-06 09:52:16 +00:00
James Read
a67b5b4e8f 3k release (#693)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
2025-11-06 00:44:19 +00:00
James Read
a3c5114615 fix: #685 - show navigation (and picocrank upgrade) (#692)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-06 00:42:56 +00:00
jamesread
28c813762f fix: #685 - show navigation (and picocrank upgrade) 2025-11-06 00:38:56 +00:00
James Read
d94f2aca1c Addresses concurrent map read and write (#688)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
2025-11-05 00:46:10 +00:00
jamesread
055472902d fix: #686 - concurrent map read and write - in entity instances 2025-11-04 23:30:14 +00:00
jamesread
2b24daa6d0 fix: Address concurrency issue in entities storage 2025-11-04 23:18:41 +00:00
jamesread
294e33d110 fix: #686 - concurrent map read and write 2025-11-04 23:03:45 +00:00
jamesread
d3cd876eec doc: Added API docs generation tool for 3k
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
2025-10-31 09:26:40 +00:00
James Read
52cd5f255a doc: Better default config that includes security examples and more doc links (#682) 2025-10-31 09:19:49 +00:00
jamesread
2b1f9a9247 doc: Better layout of default config security section 2025-10-31 09:18:45 +00:00
jamesread
6782156a58 doc: Add date id 2025-10-30 21:33:10 +00:00
jamesread
f1250f9caf doc: Add more security examples to default config 2025-10-30 21:24:13 +00:00
jamesread
0bf313a3f7 doc: Move defaultPolicy to the end in the config 2025-10-30 21:14:49 +00:00
jamesread
092661c7eb doc: Better default config that includes security examples and more doc links 2025-10-30 21:10:58 +00:00
James Read
2a6d9e4f68 3k release (#681)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-30 16:07:23 +00:00
jamesread
83f45d71bf fix: Several coderabbit suggestions on next branch 2025-10-30 15:51:40 +00:00
jamesread
79a71099f9 fix: fmt api.go 2025-10-30 15:26:29 +00:00
jamesread
e6a02ac614 chore: fix various cyclo checks 2025-10-30 15:25:57 +00:00
James Read
e0167c9e42 fix: Require guests to login (#678) 2025-10-30 13:22:39 +00:00
James Read
7abffedb14 Merge branch 'next' into fix-require-guests-login 2025-10-30 13:15:37 +00:00
James Read
d32db6483e chore: reduce cyclo complexity in service (#680) 2025-10-30 13:05:19 +00:00
jamesread
44b518a5b2 fix: panic when loading sessions.yaml 2025-10-30 13:04:41 +00:00
jamesread
a4e50bfb54 fix: panic when executing action with no arguments 2025-10-30 13:04:12 +00:00
jamesread
a8f5e25454 chore: Conflict in AGENTS.md 2025-10-30 12:57:59 +00:00
jamesread
c3d5da1981 chore: reduce cyclo complexity in service 2025-10-30 12:52:58 +00:00
jamesread
7a1c4d3efa fix: Test authRequireGuestsToLogin redirect
Some checks failed
Buf CI / buf (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-10-30 11:58:51 +00:00
jamesread
c89979ddb2 fix: Wait for login UI to appear in authRequireGuestsToLogin test 2025-10-30 11:05:14 +00:00
James Read
430aab638b Merge branch 'next' into fix-require-guests-login 2025-10-30 09:20:24 +00:00
jamesread
961ddac193 fix: Duplicate ACL rendering bug in UserControlPanel 2025-10-30 09:17:55 +00:00
James Read
03ac3b5fa7 feat: ActionDetailsView component, show timeout and logs for action. … (#676) 2025-10-30 02:15:01 +00:00
jamesread
d21f06e555 fix: Require guests to login 2025-10-30 02:13:40 +00:00
James Read
f25b456c3d Merge branch 'next' into fix-output-streaming 2025-10-30 00:25:49 +00:00
jamesread
e1db1e7be5 fix: Add big error handling for action details view 2025-10-30 00:24:50 +00:00
jamesread
19c3b67cdd fix: fix panic in pagination if we get a bad request 2025-10-30 00:12:06 +00:00
jamesread
b9d859ada2 fix: fix start action button in ActionDetailsView.vue 2025-10-30 00:08:12 +00:00
jamesread
61fc771ac3 fix: Race condition and speedup in accessing streaming clients 2025-10-30 00:03:37 +00:00
jamesread
e0fd10a6ec fix: calculate duration correctly in ExecutionView.vue 2025-10-29 23:35:37 +00:00
James Read
2a5732cc27 feat: enable JSON logging support with OLIVETIN_LOG_FORMAT=json envir… (#677) 2025-10-29 23:03:15 +00:00
jamesread
57390be16f feat: ActionDetailsView component, show timeout and logs for action. Fix output streaming. 2025-10-29 22:45:26 +00:00
jamesread
8a6d61c260 feat: enable JSON logging support with OLIVETIN_LOG_FORMAT=json environment variable 2025-10-29 22:44:49 +00:00
jamesread
f337e05eaf chore: start next cycle 2025-10-29 22:39:54 +00:00
jamesread
6c6d07bf4f fix: #674 Use JSON options for API handler
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-27 20:40:15 +00:00
jamesread
d54f2307c7 fix: Use tree for webui nfpm packages
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-10-27 16:27:32 +00:00
jamesread
49dcc7fb46 fix: goreleaser bug for webui 2025-10-27 16:20:48 +00:00
jamesread
2ea35697d0 fix: #672 Empty execution tracking ID in InternalLogEntry 2025-10-27 15:42:13 +00:00
jamesread
a551589840 feat: #428 Initial support for include directive in config files 2025-10-27 15:32:30 +00:00
jamesread
fcd3ccc59a fix: authRequireGuestsToLogin config, and config loading improvements 2025-10-27 14:56:32 +00:00
jamesread
dddc0417c2 fix: #673 Testing fix for broken deb packages 2025-10-27 14:33:40 +00:00
jamesread
d5eb74e738 fix: Include "fix" in the right place in the release notes 2025-10-27 14:22:21 +00:00
jamesread
9fbaa8671f fix: Banner message support 2025-10-27 14:20:26 +00:00
James Read
a915a654cb bugfix: #639 Exec support, disallow URL and similar arguments with (#671)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-26 19:02:22 +00:00
jamesread
c86bf629f9 bugfix: Added nil checks 2025-10-26 17:32:26 +00:00
jamesread
c917d1b1e7 bugfix: #639 Exec support, disallow URL and similar arguments with 2025-10-26 16:40:44 +00:00
James Read
1cb12b203e Local user login fixes (#669) 2025-10-26 14:39:03 +00:00
James Read
2a21d74e35 Merge branch 'main' into next 2025-10-26 14:22:25 +00:00
jamesread
8686a5629e fix: User Information panel and login/logout flow 2025-10-26 13:42:06 +00:00
jamesread
43cfe41378 fix: Issues with login form and local auth 2025-10-26 13:24:22 +00:00
James Read
280234b138 fix dark mode styles (#668)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-26 00:23:09 +00:00
jamesread
02ec8eeb65 fix: Upgraded femtocrank to fix dark mode styles 2025-10-26 01:10:43 +01:00
jamesread
ef5a67e7b8 fix: Upgrade femtocrank for dark styles 2025-10-26 00:47:46 +01:00
James Read
eb2463aa2d 3k release: Connect RPC migration and authentication refactoring (#666)
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-24 23:42:39 +00:00
jamesread
a7e7bf869e fix: WIP on login regression 2025-10-25 00:31:02 +01:00
jamesread
0dd9e9b2b7 fix: guard against nil session storage 2025-10-25 00:22:28 +01:00
jamesread
aa8322c354 fix: guard against nil session storage 2025-10-25 00:20:56 +01:00
jamesread
956e74a6b3 fix: Don't log SID (!) SECURITY 2025-10-25 00:20:35 +01:00
jamesread
c9ff4d1a68 fix: broken go.mod 2025-10-25 00:20:15 +01:00
jamesread
88cc1ab080 chore: Makefile whitespace 2025-10-24 23:55:42 +01:00
jamesread
3b8bc49b04 feat: Fixed session management and ripped out the rest of gRPC 2025-10-24 23:48:48 +01:00
jamesread
31ea8507f5 doc: Typo in agents, fix undefined var DATE in pipeline 2025-10-24 22:51:31 +01:00
jamesread
62af851b2c fix: Error getting absolute path for config.yaml 2025-10-24 22:38:26 +01:00
James Read
2a764acde6 chore: Don't run release pipeline on PR branches (#665) 2025-10-24 22:16:45 +01:00
James Read
02e2ac1676 fix: Listen address fields were not being loaded from config.yaml (#664) 2025-10-24 22:16:02 +01:00
jamesread
c89579840b chore: Don't run release pipeline on PR branches 2025-10-24 22:14:00 +01:00
jamesread
38d81fafe2 fix: Listen address fields were not being loaded from config.yaml 2025-10-24 22:06:32 +01:00
James Read
8b2b85c3d0 fix: Argument form start button, and input validation was also broken! (#663) 2025-10-24 21:57:23 +01:00
James Read
76a33e2e54 chore: Remove some old dead code (#662) 2025-10-24 21:57:07 +01:00
jamesread
fa94357374 chore: Stop AI agents adding superflous comments 2025-10-24 21:31:54 +01:00
James Read
439e952a25 fix: sosreport contains pwd and abs paths (#660) 2025-10-24 20:52:01 +01:00
jamesread
3dfbbcc770 doc: Switch to issue types
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-24 19:01:04 +01:00
jamesread
77e8c37599 fix: docker latest-3k tag 2025-10-24 18:37:47 +01:00
jamesread
d3aa3b25b0 fix: doc typo
Some checks failed
Build & Release pipeline / build (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-10-24 01:24:33 +01:00
jamesread
d944b09c51 chore: Move up the saving of integrationtests 2025-10-24 01:18:14 +01:00
jamesread
b9851adfde doc: Add upgrade guide to readme 2025-10-24 01:16:48 +01:00
jamesread
45f9c18bc3 fix: enable main releases on 3k 2025-10-24 01:13:01 +01:00
jamesread
5d947f5a32 fix: enable main releases on 3k 2025-10-24 01:01:22 +01:00
jamesread
754d216827 fix: 3k version in semrel 2025-10-24 00:54:28 +01:00
jamesread
3d902295ad chore: disable semrel on inactive 2k branch 2025-10-24 00:47:49 +01:00
jamesread
5f8cd60736 chore: disable semrel on inactive 2k branch 2025-10-24 00:46:51 +01:00
jamesread
ae360100ce chore: add build service step to build and release workflow 2025-10-23 23:43:43 +01:00
jamesread
4a851355a8 chore: run pipeline on all branches 2025-10-23 23:36:38 +01:00
jamesread
54d3c65df3 chore: upgrade icons package 2025-10-23 23:34:54 +01:00
jamesread
58ba8eeeb9 chore: Simplify semrel pipeline 2025-10-23 23:34:39 +01:00
jamesread
e1e9cd9c35 feat: Begin automating 3k releases 2025-10-23 23:24:10 +01:00
jamesread
2a5fe71458 chore: wip OliveTin3k 2025-10-23 23:18:10 +01:00
jamesread
cbb163726e chore: wip OliveTin3k 2025-10-23 23:16:39 +01:00
dependabot[bot]
6836062b00 build(deps): bump vite from 7.1.9 to 7.1.11 in /frontend (#654)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-22 09:22:04 +00:00
dependabot[bot]
339dbe6dbd build(deps): bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 in /service (#641)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 06:23:50 +00:00
dependabot[bot]
a24a7fbd01 build(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.1 in /service (#652)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Read <contact@jread.com>
2025-10-11 06:16:47 +00:00
jamesread
c9c781b197 fix: webui directory search
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-10-11 01:16:06 +01:00
jamesread
b0f24811b2 fix: stylelint and webui cleanup 2025-10-11 01:12:07 +01:00
jamesread
3884dc6d0a fix: codestyle workflow 2025-10-11 00:59:49 +01:00
jamesread
91dfe2437e fix: increase integration test timeout 2025-10-11 00:56:56 +01:00
jamesread
60814b97e2 chore: fix gocyclo issues 2025-10-11 00:52:18 +01:00
jamesread
b330fbd1a5 fix: all broken integration tests 2025-10-11 00:45:41 +01:00
jamesread
7d5fa999e5 fix: Move Iconify to v3, and version it via npm 2025-10-01 23:28:55 +01:00
jamesread
a464e6a445 fix: broken tests after changing the way arguments are parsed
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-10-01 22:20:56 +01:00
Tim Green
a26a8bb032 chore: update the docs link for timeouts in the error message (#651)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-10-01 07:18:39 +01:00
jamesread
7345744e41 chore: frontend updates
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-09-07 01:33:46 +01:00
jamesread
570c0ba087 chore: Repair output streaming, lots of css/go lint
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
2025-09-06 08:42:13 +01:00
dependabot[bot]
60c0c5db27 build(deps-dev): bump tmp from 0.2.3 to 0.2.4 in /integration-tests (#638)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Read <contact@jread.com>
2025-09-02 20:56:57 +01:00
jamesread
4a847f0587 Merge branch 'main' of github.com:OliveTin/OliveTin
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-08-20 00:05:47 +01:00
jamesread
6b342cbedb chore: Port huge amount of code to OliveTin 3k 2025-08-20 00:05:40 +01:00
jamesread
f46a02fced chore: tests wip
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-08-19 17:29:40 +01:00
James Read
3dd7aaff88 Update restapi_auth_oauth2.go
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-08-15 18:26:50 +02:00
James Read
7d4edeb60a Update build-tag.yml (#640) 2025-08-15 18:23:19 +02:00
James Read
387f1d9c1a Update build-snapshot.yml 2025-08-15 18:21:36 +02:00
James Read
c526fa323e Update AI.md
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-08-08 00:07:16 +01:00
jamesread
17c716c599 chore: fix build error 2025-08-03 22:41:01 +01:00
James Read
c5b49b33ab Update README.md
Some checks failed
Buf CI / buf (push) Has been cancelled
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-08-03 22:22:17 +01:00
jamesread
2a73b58255 chore: merge main 2025-08-03 22:17:35 +01:00
jamesread
a62d58f119 chore: OliveTin 3k progress 2025-08-03 22:10:51 +01:00
James Read
e02ce2be4e Update README.md (#637)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-07-31 19:45:01 +00:00
James Read
21ad5871ce doc: Fix broken link (#636)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-31 14:02:27 +00:00
James Read
d4d3193c1d bugfix: Argument confirmations stopped working (#627) (#632)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-29 21:51:34 +00:00
dependabot[bot]
10e5a92cbe build(deps): bump github.com/docker/docker from 28.3.1+incompatible to 28.3.3+incompatible in /service (#635)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-29 21:19:17 +00:00
James Read
a06299bd9e feature: stylemods, for side by side buttons, and XL images (#634)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-07-28 23:02:04 +00:00
James Read
2d4a3fc048 bugfix: Crash in OAuth2 userdata, and option to log user data (#631) 2025-07-28 21:46:08 +00:00
James Read
81ef166d78 bugfix #627 (#630)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-28 09:24:38 +00:00
dependabot[bot]
d4fe9eaa79 build(deps): bump form-data from 4.0.0 to 4.0.4 in /integration-tests (#626)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 06:19:32 +00:00
James Read
af75afa82f bugfix: #401 Duplicate lines in output (#623)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-19 23:34:59 +00:00
James Read
b91c0d7e45 feat: Style update for buttons, reduce style duplication (#621)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-19 11:51:19 +00:00
James Read
e922baa2e0 feat: #104 Checkboxes / booleans at last! (#620)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-19 02:10:41 +01:00
James Read
fcd1879d09 feat: Entities in icon field, and other cleanup stuff (#619)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-07-17 15:04:15 +00:00
James Read
a8ac719af7 feat kill acl (#618)
Some checks failed
Buf CI / buf (push) Has been cancelled
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-13 09:08:05 +00:00
jamesread
b99b3f4345 chore: Various codestyle fixes
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-07-12 23:46:50 +01:00
jamesread
b16ce074ea chore: Various codestyle fixes 2025-07-12 23:44:24 +01:00
James Read
54447774d1 fix: #616 - JSON parsing of ints to float64 (#617) 2025-07-12 22:02:50 +00:00
James Read
260477e5e8 Update README.md (#615)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-07-09 16:09:26 +00:00
James Read
e559c32c37 doc screenshots (#614) 2025-07-09 15:54:23 +00:00
James Read
2c7b33b730 chore: Dep update 2025-07-09 (#613)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
2025-07-09 10:24:59 +00:00
James Read
1970311ff5 feat: Triggered actions get previous action args #607 (#611)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-06-22 22:08:07 +00:00
James Read
433456986d fix: #603 - Dashboards that have fieldsets without entities are hidden (#609) 2025-06-22 21:09:03 +00:00
James Read
e38361f3d7 fix: gofmt/gocylo leftover from old PR (#610) 2025-06-22 20:42:57 +00:00
James Read
1ffdd93ddf fix: Redact password arguments in logs #594 (#601)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-06-06 21:00:56 +00:00
James Read
18c5599704 fix: Datetime mangling for Android phones #564 (#602) 2025-06-06 20:41:54 +00:00
James Read
224d7f40ed feat: Add unauthenticated readyz endpoint (#600) 2025-06-06 16:02:00 +00:00
James Read
1357eae9b8 fix: Empty dashboards are hidden (#599)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-06-06 15:11:02 +00:00
James Read
b2e7509959 feat: use sans-serif font everywhere (#597)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-06-01 11:51:17 +00:00
James Read
cebab32514 bug: Hide password values in deeplinks #594 (#596)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-05-31 19:54:44 +00:00
James Read
7110399d41 fix: Bug that caused duplicate links (eg Diagnostics and Logs) in the nav bar (#595) 2025-05-31 19:20:37 +00:00
James Read
74f0930dcc feat: #582 ServiceMain for Windows (#593)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-05-30 08:30:58 +00:00
James Read
c20eea29cd feat: win unicode flag (#590)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-05-26 07:33:22 +00:00
James Read
8c073bf45f feature: Intelligent version comparison #575 (#580)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-05-02 15:53:56 +00:00
jamesread
fcfa007cec build: Add unit tests for regex (ref: #578)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-05-01 16:30:29 +01:00
dependabot[bot]
b83b7a4c42 build(deps-dev): bump base-x from 3.0.9 to 3.0.11 in /webui.dev (#577)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 06:48:12 +00:00
James Read
0a7f3f3226 bugfix: #503 Fix possible race condition in onfileindir* (#576)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-26 20:42:12 +00:00
James Read
633e513697 feature: (#568) Separator allowed in usergroup line for trusted headers (#572)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-22 14:35:49 +01:00
James Read
eb2721c023 chore: Cleanup argument handling (#571)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-21 08:50:59 +00:00
James Read
765c698a9b feature: Easier loading of custom-webui icons with simplified paths (#567)
Some checks failed
Buf CI / buf (push) Has been cancelled
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-19 23:06:17 +00:00
James Read
c19428f6b6 feature: Policy support - allow hiding daignostics and logs (#569) 2025-04-19 23:05:49 +00:00
James Read
f02982b451 docs: Add AI policy (#563)
Some checks failed
Buf CI / buf (push) Has been cancelled
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-14 23:04:34 +00:00
jamesread
6ffb0cedbc chore: Remove surious dep update 2025-04-14 17:33:24 +01:00
James Read
775b3d3ca6 feat: Smaller width buttons on mobile (#561)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-04-13 18:37:58 +00:00
jamesread
6ad001619d build: feat/feature changelog prio
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-09 10:34:13 +01:00
James Read
db28e8915b feature: Include kubernetes-client (#559)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-08 21:39:21 +00:00
jamesread
bb4969c9ac build: Fix webui codestyle job
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-04-08 22:27:21 +01:00
jamesread
aef70c0e1b build: Fix webui codestyle job 2025-04-08 22:19:36 +01:00
jamesread
27c287c2de build: Code format 2025-04-08 22:14:23 +01:00
Noah Perks Sloan
ec1f974f67 feat: links to argument forms (#551)
Co-authored-by: James Read <contact@jread.com>
2025-04-08 21:07:01 +00:00
James Read
4ccfd0f993 fix: 404 in docs for "big error messages" (#558) 2025-04-08 20:54:37 +00:00
jamesread
c5eaa35fb0 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2025-04-08 21:49:22 +01:00
jamesread
24ba4fb574 build: cyclo ignore "getexecutionscount 2025-04-08 21:49:10 +01:00
Noah Perks Sloan
b742bd89c4 feat: support multiple user groups (#555) 2025-04-08 20:31:55 +00:00
James Read
2fcc0a63a0 feature: #543 Rate limits are now per action-entity instead of per action (#557)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-08 00:28:32 +00:00
jamesread
ba29325c15 feature: #543 Rate limits are now per action-entity instead of per action 2025-04-08 01:18:55 +01:00
James Read
88f639d29f chore: Cleanup executor shell passing (#556)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-07 20:48:45 +00:00
James Read
fa44e958d8 chore: Big dependency update (#553)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-06 22:26:51 +00:00
David Q
182548e0dc feat: add syntax for interpolating environment variables into config file (#548)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Co-authored-by: James Read <contact@jread.com>
2025-04-05 22:36:10 +00:00
David Q
c3097e40db fix: reloading config after k8s configmap replacement (#552)
Co-authored-by: James Read <contact@jread.com>
2025-04-05 22:28:28 +00:00
jamesread
2b66414a93 build: Add missing build-snapshot job on PR 2025-04-05 23:23:47 +01:00
jamesread
7da8a5bc38 buf dep update
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Buf CI / buf (push) Has been cancelled
2025-04-05 01:02:47 +01:00
jamesread
2bc45e9a09 build: cannot set cwd with buf-action 2025-04-05 01:00:00 +01:00
jamesread
fd3c6087f0 working directory for buf 2025-04-05 00:51:32 +01:00
jamesread
c0b8dd71db Enable manual trigger for buf 2025-04-05 00:48:09 +01:00
jamesread
1164e5fae2 working directory for buf 2025-04-05 00:43:26 +01:00
jamesread
902d4ed819 build: Switch to buf2 2025-04-05 00:15:57 +01:00
James Read
2320a56dd9 build: Update cache path for go (#550)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-03 10:48:15 +00:00
James Read
2981fc4c1f doc: Point pull request template to contributors guide (#549)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-02 22:47:41 +00:00
Noah Perks Sloan
8865331da2 feat: enable JWT auth from a header (#547)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-04-02 19:14:43 +01:00
James Read
709d6ac2ad feature: Allow rendering output as HTML (#544)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-03-28 23:48:26 +00:00
jamesread
8d4e335dda fix: Invalid path to systemd file
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-03-23 01:02:22 +00:00
jamesread
44a9de080c docs: Windows was not bold. 2025-03-23 00:28:07 +00:00
jamesread
9bb17badad docs: Links and make targets 2025-03-23 00:26:52 +00:00
James Read
ff31abe66c refactor: Project directories (#541)
Some checks failed
Build Snapshot / build-snapshot (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
Buf CI / buf (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
2025-03-22 01:06:59 +00:00
James Read
b4c886d5d3 refactor: systemd file does not need to be in root dir (#538) 2025-03-22 01:06:22 +00:00
dependabot[bot]
e0bc1d86f6 build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#540)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-22 00:42:53 +00:00
James Read
270f20ec75 refactor: Moved proto files into own folder (#537)
Some checks failed
Buf CI / buf (push) Waiting to run
Build Snapshot / build-snapshot (push) Waiting to run
Codestyle checks / codestyle (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-03-21 02:41:41 +00:00
James Read
486253b253 feature: Docker ce (#536) 2025-03-21 00:15:44 +00:00
James Read
6b9c6c8b9c refactor: Switch to conventional scripts, rather than custom script (#535) 2025-03-20 23:04:36 +00:00
dependabot[bot]
1275934ac1 build(deps): bump axios from 1.7.4 to 1.8.4 in /integration-tests (#534)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Read <contact@jread.com>
2025-03-20 23:00:03 +00:00
jamesread
bbc6095e36 doc: social banner 2025-03-20 22:44:25 +00:00
jamesread
7906f2d363 refactor: dep update 2025-03-20 21:55:17 +00:00
jamesread
d45bd887c2 security: dep update 2025-03-20 21:44:06 +00:00
James Read
7788f58aac feature: persist local sessions across restart (#522)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-02-21 22:26:15 +00:00
James Read
f3bc82311d feature: Allow arbitary HTML on argument form (useful for descriptions) (#519) 2025-02-21 17:57:02 +00:00
James Read
cae5d296ca bugfix: Fixed broken log message for Execution finished on websocket (#520) 2025-02-21 17:56:37 +00:00
James Read
5cd5bd2a25 bugfix: Default config now uses "triggers" instead of "trigger", and shows HTML (#521) 2025-02-21 17:53:19 +00:00
James Read
6550223ee4 fmt: code style (#518)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2025-02-19 23:18:02 +00:00
James Read
39368d511a feature: Limit log history to prevent browser lag and grpc encode failures (#507)
* feature: Limit log history to prevent browser lag and grpc encode failures

* cicd: Simplify if/else chain to switch

* cicd: Lint failure in JS because of a trailing space

* cicd: Coderabbit fixes on PR

* cicd: Move config sanitize into correct place

* cicd: Handle negative startOffset, negative endOffset and empty logs

* Update internal/executor/executor.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* cicd: Fix getLogs segfault

* cicd: Fix code rabbit nitpicks

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-02-19 23:17:06 +00:00
James Read
0a4bcc6423 cicd: Update .goreleaser.yml file to Feb 2025 syntax (#517) 2025-02-19 16:57:32 +00:00
jamesread
56ab1cec8f cicd: Textarea resizing [skip ci] 2025-02-18 17:49:54 -08:00
jamesread
12f87ca6e1 feature: Add support for raw textboxes #490 2025-02-18 17:49:54 -08:00
James Read
2fc7c23416 feature!: Trigger changed Triggers, allowing multiple actions (#515)
* feature!: Trigger changed Triggers, allowing multiple actions

* bugfix: Warning message on trigger loops, prevented NPE

* cicd: Fix cyclo complexity with triggers
2025-02-19 00:00:33 +00:00
James Read
2cf538bab1 bugfix: Systemd unit now waits for network and filesystem #495 (#514)
Some checks are pending
Build Snapshot / build-snapshot (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
Codestyle checks / codestyle (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
2025-02-18 23:17:56 +00:00
dependabot[bot]
906b6c5783 build(deps): bump serialize-javascript and mocha in /integration-tests (#513)
Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.4.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.4.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-18 17:14:25 +00:00
jamesread
6d43ebef44 docs: Change maturity label
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-23 14:58:26 +00:00
James Read
c04203e671 Update README.md (#508)
Some checks are pending
Build Snapshot / build-snapshot (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
2025-01-22 14:07:21 +00:00
dependabot[bot]
bf93707787 build(deps): bump github.com/MicahParks/jwkset from 0.5.17 to 0.7.0 (#506)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Bumps [github.com/MicahParks/jwkset](https://github.com/MicahParks/jwkset) from 0.5.17 to 0.7.0.
- [Release notes](https://github.com/MicahParks/jwkset/releases)
- [Commits](https://github.com/MicahParks/jwkset/compare/v0.5.17...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/MicahParks/jwkset
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-12 21:39:18 +00:00
Bjorn Lammers
f0d70f0c15 Migrate dashboard-icons to homarr-labs
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-06 22:21:35 +00:00
jamesread
17d9e29f19 cicd: Add responsibility labels to all new issues
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2024-12-30 02:05:05 +00:00
jamesread
be7168d9c5 cicd: Issue responsibility bugs 2024-12-30 01:59:27 +00:00
jamesread
d36b23832c cicd: Issue responsibility bugs 2024-12-30 01:54:36 +00:00
jamesread
b6429e9bc7 cicd: Add issue responsibility labels 2024-12-30 01:45:51 +00:00
jamesread
7ddc112b2c doc: CONTRIBUTING - add more guidelines
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2024-12-23 17:14:55 +00:00
jamesread
10a473ca1c security: Big dependency update
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2024-12-22 01:59:43 +00:00
jamesread
c7207d1ee6 security: Big dependency update 2024-12-22 01:58:19 +00:00
jamesread
c585762ba8 cicd: Standardize the automated code generation 2024-12-22 01:48:21 +00:00
James Read
476838d59a bugfix: Local users now work with a single usergroup (#486)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2024-11-24 21:22:24 +00:00
James Read
f0b1cefb72 feature: OAuth2 early support for CertBundles (#474) and Usergroups (#477) (#485)
Some checks are pending
Build Snapshot / build-snapshot (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
Codestyle checks / codestyle (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
2024-11-24 10:00:25 +00:00
James Read
b4a555e3da bugfix: #481 StartActionAndWait now allows arguments. (#483) 2024-11-24 06:56:27 +00:00
James Read
79a4119351 security: Update cross-spawn vulnerable dependency (#482) 2024-11-24 06:56:16 +00:00
jamesread
851d0dac84 doc: Fix #478 - easy.cfg typo [skip ci] 2024-11-23 16:31:23 -06:00
James Read
655a7f205d feature: Improved auth log messages (#476)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2024-11-14 22:00:08 +00:00
James Read
13e48dded2 feature: Better guest handling (#472)
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
* feature: Better guest handling

* bugfix: Better handling of requiring guests to login
2024-11-09 23:27:09 +00:00
dependabot[bot]
344df3739d build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#473)
Some checks are pending
Build Snapshot / build-snapshot (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
Codestyle checks / codestyle (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-09 00:13:32 +00:00
jamesread
0d981773b3 fmt: gofmt 2024-11-08 23:54:32 +00:00
jamesread
8d4ddd36cd cicd: Goreleaser tikeout
Some checks failed
Build Snapshot / build-snapshot (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Codestyle checks / codestyle (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
2024-11-02 07:35:36 +00:00
James Read
209234c09f bugfix: Still hide an empty actions section if a section is currently selected (#469) 2024-11-02 01:32:02 +00:00
James Read
9bcb2d80dc bugfix: Logs table only updating on start, not finish (#467)
Some checks are pending
Build Snapshot / build-snapshot (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
Codestyle checks / codestyle (push) Waiting to run
DevSkim / DevSkim (push) Waiting to run
2024-11-02 00:47:13 +00:00
jamesread
de504f5f80 cicd: Lint issue 2024-11-01 23:23:02 +00:00
jamesread
8fe91101db feature: Log search box that is less visually offensive 2024-11-01 23:23:02 +00:00
James Read
8717997b0e feature: Hovering over the execution dialog title shows the action ID and execution ID (#468) 2024-11-01 22:49:09 +00:00
James Read
1bfb7c4b28 feature: Logs table creates an entry as soon as the execution is started (#464) 2024-11-01 21:23:22 +00:00
James Read
964dc7b48a feature: Rerun button on execution dialog, easily rerun actions! (#465) 2024-11-01 20:49:45 +00:00
James Read
ceb0a78180 bugfix: Logout cookies causing a conflict (#461) 2024-11-01 20:36:38 +00:00
James Read
9117163316 bugfix: Login form was not displayed if only OAuth2 was configured (#457)
* bugfix: Login form was not displayed if only OAuth2 was configured

* bugfix: npe in AuthOAuth2Provider

* test: Try and fix flakey tests
2024-10-27 08:56:28 +00:00
James Read
d3ad811ac5 bugfix: Removed hardcoded dependency on GitHub in OAuth2 flow (#458) 2024-10-26 22:30:08 +00:00
James Read
ee26fe6b50 ci github int tests settle (#459)
* ci: Allow more time for GitHub tests to settle

* ci: Run build-snapshot on any push, but dont dupe with PR
2024-10-26 22:10:33 +00:00
James Read
2950b59514 cicd: Devskim actions upgrade (#456) 2024-10-25 13:44:08 +00:00
James Read
be5ddda020 feature: freebsd support (#454) 2024-10-23 12:52:25 +00:00
James Read
16b07283b0 feature: Add riscv build! (#453) 2024-10-23 10:54:43 +00:00
jamesread
d7814ff6df bugfix: If a button is disabled in the API, render it disabled [skip ci] 2024-10-20 23:03:10 +01:00
James Read
be9b2a7c78 bugfix: Cookie expiry for OAuth2 & Local set to 1 year, not session (#451) 2024-10-20 21:58:12 +00:00
James Read
80e5b5b0c1 feature: Cleanup auth code (#444) 2024-10-19 19:33:27 +00:00
James Read
0283b51eca feature: Login/Logout links (#443)
* feature: Login/Logout links

* cicd: codestyle
2024-10-19 07:49:41 +00:00
jamesread
1af2e92132 feature: Local user login! 2024-10-18 01:20:04 +01:00
James Read
71ad5d2e3a feature: local auth! (#441)
* feature: local auth!

* cicd: contentLogin -> content-login

* bugfix: CSS codestyle
2024-10-17 13:23:01 +00:00
James Read
32b5fee108 bugfix: Fixed crash when requesting execution status that could not be found (NPE) (#440) 2024-10-15 10:48:55 +00:00
James Read
de81ec00fd feature: mix of bits (#439)
* feature: mix of bits

* bugfix: codestyle
2024-10-15 07:47:51 +00:00
Simon Dahlbacka
7cc07158ab bugfix: Turns out this should now be 403 instead (#438) 2024-10-14 13:53:23 +00:00
jamesread
3b8976fd51 feature: Login screen 2024-10-14 09:26:33 +01:00
jamesread
fb7e650267 feature: OAuth2 Redirect URL 2024-10-14 09:25:21 +01:00
jamesread
6a7187fb5b feature: OAuth2! :-D 2024-10-14 09:25:21 +01:00
James Read
b31cdf15a2 feature: Email argument type (#433)
* feature: Email argument type

* bugfix: Fix error if additional links is null
2024-10-13 18:40:16 +00:00
jamesread
6e0e0e8133 cicd: cleanup console log 2024-10-13 01:10:47 +01:00
jamesread
706441799f feature: Custom navigation links in the nav (sidebar/topbar) 2024-10-13 00:35:47 +01:00
jamesread
0b66bc7bbd cicd: Labels on issue templates [skip ci] 2024-10-13 00:35:28 +01:00
jamesread
86e876a9c9 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-10-12 23:18:29 +01:00
jamesread
8b33061bbb cicd: Remove unused Jenkinsfile 2024-10-12 23:15:06 +01:00
James Read
411f1bff1c feature: Style the currently selected section in nav, and few minor style improvements (#429)
* feature: Style the currently selected section in nav, and few minor style improvements

* bugfix: No unit on 0 length
2024-10-12 21:45:26 +00:00
James Read
eee4c4e404 bugfix: Auth in the wrong place (#430) 2024-10-12 21:14:09 +00:00
James Read
a187c8c8a0 feature: Slightly nicer looking logs and diagnostics pages (#427) 2024-10-11 17:40:40 +00:00
Simon Dahlbacka
35dca50863 Make it possible to redirect to a login url unless authenticated (#347)
* feature: Try to navigate to UrlOnUnauthenticated if set and not authenticated

* refactor: use better naming

* feature: default to allow guest
2024-10-11 17:36:31 +00:00
Simon Dahlbacka
00856f15a7 feature: pass ot_ args to shellAfterCompleted (#420)
* feature: pass ot_ args to shellAfterCompleted

* refactor: Implement this the right way

---------

Co-authored-by: James Read <contact@jread.com>
2024-10-11 14:59:02 +00:00
James Read
d1c67a9dd8 depbump: js micromatch 4.0.8 (#426) 2024-10-10 23:38:35 +00:00
James Read
6c289506c2 bugfix: Fixed unusual issues that slipped through with shellAfterCompleted (#425)
* bugfix: Fixed unusual issues that slipped through with shellAfterCompleted #419 #420

* cicd: Fix broken style lint
2024-10-10 23:15:03 +00:00
James Read
5d82ae7680 feature: Log username with each request #422 (#424) 2024-10-10 20:50:47 +00:00
James Read
9737886839 feature: Better websocket error message (#417) 2024-10-02 00:19:49 +00:00
James Read
1d9502d800 Feature better 404s (#416)
* feature: CSS on actions

* feature: Less crashes when things are not found
2024-10-01 23:21:37 +00:00
James Read
c0a18f82a5 feature: Link to log outputs (#415) 2024-10-01 23:17:09 +00:00
jamesread
9723a38ca1 bugfix: Crash on trying to start a non-existant action from the API 2024-10-01 21:19:17 +01:00
James Read
8258a758d2 bugfix: Dashboards with multiple spaces in the name could not be navigated properly (#408) 2024-09-16 22:36:40 +00:00
James Read
044613ae9a bugfix: Null action map, causing crash on startup actions (#407) 2024-09-11 16:19:52 +00:00
James Read
76c9171356 feature: Add OT_ vars to each execution (#406) 2024-09-10 22:52:46 +00:00
James Read
f8c330aae3 feature: Better feedback on shellAfterCommand, and mark OliveTin specific output with "OliveTin::" in logs (#403) 2024-09-02 22:59:19 +00:00
jamesread
d4bd7dd586 doc: shellAfterCompleted should use output, not stdout [skip ci] 2024-09-02 21:14:34 +01:00
Mitch Brown
fa20f6a63a Fixed typo 2024-09-01 00:22:45 +01:00
James Read
b9ce695616 feature: Nicer default colors (catpuccin/frappe (#395) 2024-08-30 20:48:17 +00:00
James Read
3f1dbf1130 bugfix: System users can now be used in ACLs (#396) 2024-08-30 20:48:07 +00:00
James Read
6413e51cf5 feature: Exec permission denied now marks the action as "blocked" and adds a log message. Timeouts also add a documentation link to the command output. Tags always set. (#398) 2024-08-30 20:36:13 +00:00
James Read
defcf6d26e fmt: Rebuilt ACL code to have less boilerplate (#397) 2024-08-30 20:30:44 +00:00
James Read
2671983c43 bugfix: Execution dialog duration no longer has issues displaying actions that were never started. (#394) 2024-08-30 20:28:27 +00:00
James Read
dc9653307b feature: Logs, column for tag, and left align status. Stop nav flash (#393) 2024-08-30 20:28:12 +00:00
jamesread
eb91eb33d5 bugfix: Log for http request mangling changed to DEBUG level 2024-08-30 21:27:56 +01:00
James Read
ddb803d9b5 feature: Sidebar hiding now pure CSS & theme reloading for devs (#391) 2024-08-25 23:47:43 +00:00
dependabot[bot]
c9095b4d67 build(deps): bump axios from 1.6.8 to 1.7.4 in /integration-tests (#387)
Bumps [axios](https://github.com/axios/axios) from 1.6.8 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.8...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 22:14:35 +00:00
James Read
40158eda71 bugfix: Race condition on executor logs (#385) 2024-08-14 22:06:50 +00:00
dependabot[bot]
bbbbfceeb3 build(deps): bump github.com/docker/docker (#386)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.4+incompatible to 26.1.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.1.4...v26.1.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 22:06:06 +00:00
James Read
9ca1940834 feature: change all navigation to path based (#384)
* feature: change all navigation to path based

* bugfix: load default dashboard if actions is empty
2024-08-14 11:35:10 +00:00
James Read
37160a91f3 feature: take full page and display footer at bottom pf window (#383) 2024-08-14 06:56:30 +00:00
James Read
274d036f74 fmt: Cleanup terminal code (#381)
* fmt: Clean up OutputTerminal code

* fmt: Clean up OutputTerminal code
2024-08-09 19:04:57 +00:00
James Read
1fe0e49adb bugfix: #337 - Entity overwriting (#382) 2024-08-09 19:04:50 +00:00
James Read
7a7a07d9ad feature: Minor change to action timeout message to be more consistent with other logs (#380) 2024-08-09 14:53:02 +00:00
James Read
5b5fca0837 feature: enableCustomJs toggle to allow loading ./custom-webui/custom.js (#379) 2024-08-09 09:21:50 +00:00
James Read
fab0264d9b feature: Cleanup execution dialog, and log display status. (#378)
* feature: Cleanup execution dialog, and log display status.

* fmt: Remove empty block

* cicd: Use ActionStatusDisplay in test

* bugfix: missed promise

* bugfix: missed promise

* bugfix: Didnt return getText on ActionStatusDisplay
2024-08-08 20:46:46 +00:00
jamesread
8d839ee6ce bugfix: fsnotify event logs moved to DEBUG level, because info was spammy 2024-08-07 00:22:49 +01:00
jamesread
90efbf3159 fmt: #368 2024-08-07 00:21:02 +01:00
jamesread
17dd1b4158 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-08-07 00:17:42 +01:00
dependabot[bot]
e5f6d8ff50 build(deps): bump github.com/docker/docker (#372)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.2+incompatible to 26.1.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.2...v26.1.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 16:02:13 +00:00
jamesread
e183910b88 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-08-06 16:35:10 +01:00
James Read
8cd5b9fb46 feature: Trace log message on ExecRequest for debugging (#375) 2024-08-06 13:46:49 +00:00
wushuzh
6958445f83 Use windows compatible cmds and wait more time (#374)
* bugfix: change action request cnt type as Counter

* bugfix: fix type of act req count and typo

* bugfix: change to correct type of act req in TC

* bugfix: use Windows compatible cmds for test

* bugfix: wait more for slow test env

---------

Co-authored-by: wushuzh <wushuzh@outlook.com>
2024-08-06 13:43:16 +00:00
James Read
ef91294d5e cicd: Make grpc to fix codeql (#373) 2024-07-30 13:03:56 +00:00
jamesread
a36f286f8a cicd: Make grpc to fix codeql 2024-07-30 13:58:12 +01:00
wushuzh
d9e921950f bugfix: fix type of the prometheus metric act req as count (#370)
* bugfix: change action request cnt type as Counter

* bugfix: fix type of act req count and typo

* bugfix: change to correct type of act req in TC

---------

Co-authored-by: wushuzh <wushuzh@outlook.com>
2024-07-26 08:41:42 +00:00
wushuzh
ffcd19e748 bugfix: use cross-platform module filepath to set usedConfigDir (#369)
* bugfix: use filepath to set correct configDir in Windows

* test: improve unittest rep folder creation

---------

Co-authored-by: wushuzh <wushuzh@outlook.com>
2024-07-22 09:33:56 +00:00
jamesread
d0eb132b95 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-07-18 15:11:19 +01:00
jamesread
1cf971c092 feature: Better format for server dashboard 2024-07-18 15:11:06 +01:00
James Read
49f745be68 fmt: Set for setdir (#367) 2024-07-18 12:08:36 +00:00
wushuzh
b50824a705 feature: support basic dev tasks in Windows (#364)
* feature: support basic dev tasks in Windows

* feature: support front-end targets in Windows

---------

Co-authored-by: wushuzh <wushuzh@outlook.com>
2024-07-17 12:51:08 +00:00
James Read
69d1cc75a7 feature: css classes on displays (#363) 2024-07-16 10:01:31 +00:00
jamesread
a54ea505c9 bugfix: Allow . in enxtended variable paths 2024-07-15 22:20:49 +01:00
James Read
a1563b72ae Feature argument grid layout (#360)
* feature: Argument form now uses a grid layout, making the input boxes take up available room

* feature: Argument form now uses a grid layout, making the input boxes take up available room

* feature: Argument form now uses a grid layout, making the input boxes take up available room

* feature: Argument form now uses a grid layout, making the input boxes take up available room
2024-07-15 16:17:08 +00:00
James Read
31d7168aac feature: #350 Environment variable PORT can be used to override the default 1337, and internal services will calculate their port based on that (#358) 2024-07-15 14:28:53 +00:00
James Read
09016e1d5f feature: Entitiy data now serializes non-strings (#357) 2024-07-15 12:10:13 +00:00
James Read
652882350c cicd: Update login-action (#356) 2024-07-15 12:09:35 +00:00
James Read
20c4423799 feature: Argument labels will end with a :, unless they end with ?, : or . already (#355) 2024-07-15 08:24:54 +00:00
jamesread
bb90a5da92 feature: Use SVG icon for window maximize #343 2024-07-13 20:46:06 +01:00
jamesread
3ca3a2dd3c Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-07-13 20:43:21 +01:00
jamesread
510c48e1af bugfix: Restore default theme 2024-07-13 20:43:04 +01:00
James Read
3ac809c234 feature: Container images upgraded to Fedora 40. (#354) 2024-07-13 18:36:10 +00:00
James Read
9dd33bc3f9 bugfix: Empty environment variable names causing exec failures on Windows, thanks @sirjmann92 ! (#353) 2024-07-12 16:22:03 +00:00
James Read
897cc0e034 depbump: Upgrade Go to 1.21 (#352) 2024-07-11 22:33:36 +00:00
James Read
482ef0e5e8 feature: Helper scripts for ssh, themes, etc (#349)
* feature: Helper scripts for ssh, themes, etc

* cicd: Update goreleaser to support v2 .goreleaser.yml files
2024-07-07 22:10:33 +00:00
dependabot[bot]
e0678fc0a9 build(deps): bump github.com/rs/cors from 1.10.1 to 1.11.0 (#348)
Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.10.1 to 1.11.0.
- [Commits](https://github.com/rs/cors/compare/v1.10.1...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/rs/cors
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Read <contact@jread.com>
2024-07-07 07:01:33 +00:00
jamesread
9cb5574b99 feature: easy-ssh-docs 2024-07-07 01:06:43 +01:00
jamesread
c18b91f684 feature: Easy SSH 2024-07-07 00:55:22 +01:00
James Read
6622a6ded4 bugfix: Kill the process group, not just the parent process (#328), , thanks @ioqy, @vvrein (#346)
* bugfix: Kill the process group, not just the parent process (#328), thanks @ioqy, @vvrein

* bugfix: Kill the process group, not just the parent process (#328), thanks @ioqy, @vvrein

* bugfix: Kill the process group, not just the parent process (#328), thanks @ioqy, @vvrein
2024-07-04 00:12:28 +00:00
James Read
fb6aaa52c7 feature: New diagnostics page, showing SSH keys found. (#344) 2024-07-03 23:34:07 +00:00
James Read
a1adc2a85d bugfix: Output width in chrome (#345) 2024-07-03 23:33:58 +00:00
dependabot[bot]
3d9cb621dd build(deps-dev): bump braces from 3.0.2 to 3.0.3 in /integration-tests
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-04 00:34:34 +01:00
James Read
943b4c75aa feature: Password type (no validation) (#340) 2024-07-01 08:31:53 +00:00
James Read
fb972fae55 bugfix: Handle update failed update checks silently (#342) 2024-06-30 22:05:04 +00:00
dependabot[bot]
eb4f28dfda build(deps-dev): bump ws from 8.16.0 to 8.17.1 in /integration-tests (#335)
Bumps [ws](https://github.com/websockets/ws) from 8.16.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 05:25:03 +00:00
James Read
e36fedf4b2 Check for updates default disabled (#333)
* feature: Checking for updates is now disabled by default (#93)

* feature: Default config has checkForUpdates: false (it defaults to false anyway) to make people aware of the update checking feature
2024-06-04 18:37:59 +00:00
James Read
362a97c59e feature: No more tracking in update checking! :-) (#93) (#332) 2024-06-04 12:21:11 +00:00
James Read
c82beb61a9 feature: Vastly improved logs and tests for killing actions (#330)
* bugfix: Sleep testing

* feature: Vastly improved testing for killing actions (#328)

* cicd: Add find-flakey-tests target

* cicd: Better debugging for domStatus

* cicd: Debug flakey test

* cicd: Debug flakey test

* cicd: Is cors messing with killaction?

* cicd: Slip broken test in CI

* cicd: Skip broken test in CI

* cicd: Skip broken test in CI
2024-06-03 16:19:15 +00:00
James Read
00a8a0bf69 fmt: Center argument-wrapper #327, point 5 (#331) 2024-06-02 22:09:24 +00:00
James Read
ffc17dd73b bugfix: Terminal width fitting (#329)
* bugfix: Terminal width fitting

* bugfix: Terminal width fitting

* fmt: css codestyle issue presumably from pkg upgrade
2024-06-02 11:44:23 +00:00
James Read
238abc95ad feature: Customizable default directory, back and action icons (#326) 2024-05-31 22:43:05 +00:00
James Read
ac0f3ab6f8 feature: Live command output! (#325)
* feature: Live command output WIP

* cicd: Fix live command output build errors

* feature: Live command output WIP, remove stdout/stderr split

* feature: Live command output

* Restore output im stepExecAfter, rename so to ost for OutputStreamer

* Update executor.go

* feature: Remove output from log message, bad for long messages, binary output, and security

* feature: Live command output WIP
2024-05-31 20:56:49 +00:00
James Read
4bac315568 feature: The executor now controls the public action map - meaning actions work without the API, and pages load faster (and much more!) (#324)
* feature: The executor now controls the public action map

* bugfix: Build the action map if there are no entities!

* bugfix: Sort by title if the actions have the same config order
2024-05-27 22:52:06 +00:00
James Read
daa48b5a73 feature: OliveTin now relies on websocket, and will show a big error if it isn't available (#323)
* feature: OliveTin now relies on websocket, and will show a big error if it isnt available

* feature: OliveTin now relies on websocket, and will show a big error if it isnt available
2024-05-26 18:21:54 +00:00
James Read
c70cc864ee feature: Websocket URL is now relocatable (#322)
* feature: Websocket URL is now relocatable

* feature: Websocket URL is now relocatable
2024-05-26 17:03:12 +00:00
James Read
dc7ff40da6 bugfix: #316 Entity file change debouce timer now uses delay after initial event (#321) 2024-05-26 17:01:37 +00:00
James Read
046ffaecf4 feature: Displays now have centered text, and "fit in" a bit better (#320) 2024-05-26 16:54:11 +00:00
James Read
3904f8563d feature: Refresh dashboards when entity files change. (#319)
* feature: Refresh dashboards when entities change

* feature: Refresh dashboards when entities change

* bugfix: Concurrency, lock around websocket write
2024-05-24 22:10:38 +00:00
James Read
18423a9888 bugfix: Default load dashboard when it contains multiple spaces (#318) 2024-05-23 08:22:08 +00:00
James Read
8fbbd9b32c feature: displays have basic styling (#317) 2024-05-23 08:21:57 +00:00
guangwu
7e34efd453 chore: remove refs to deprecated io/ioutil (#309)
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
Co-authored-by: James Read <contact@jread.com>
2024-05-13 15:20:58 +00:00
jamesread
9d33e62d34 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-05-13 15:39:54 +01:00
jamesread
6b2bc0adc0 cicd: run build-snapshot against prs 2024-05-13 15:39:42 +01:00
James Read
80083fedab bugfix: Race condition in stringvariables (#311) 2024-05-13 14:10:44 +00:00
James Read
1ab35fdb36 feature: #Logs and similar in the address bar to load relevant section (#306) (#310) 2024-05-13 12:12:04 +00:00
James Read
f43ece4263 feature: Better sosreports (#308) 2024-05-13 07:01:45 +00:00
jamesread
ea1ce82ded fmt: Change publicid map debug URL 2024-05-13 01:04:21 +01:00
jamesread
c24adaafcb Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-05-06 23:01:09 +01:00
jamesread
c42875b107 cicd: update package-lock.json 2024-05-06 23:00:35 +01:00
jamesread
447ad36d4d cicd: update package-lock.json 2024-05-06 23:00:05 +01:00
jamesread
71dc467b31 cicd: Quick target to run default integration tests 2024-05-06 22:59:49 +01:00
James Read
8625e1fc0a fmt: Clean REST API Testing (#307)
* fmt: Clean REST API Testing

* fmt: Clean REST API Testing
2024-05-06 21:44:12 +00:00
James Read
638e5b7fe1 doc: fix default licenses in package.json (#304) 2024-05-01 07:38:47 +00:00
James Read
f467c69e8f Argument suggestions (#301)
* feature: Suggestions for inputs

* feature: #103 Completed suggestions support
2024-04-28 08:55:40 +00:00
James Read
8fd98874e2 feature: Save logs! #174 (#300) 2024-04-28 07:58:25 +00:00
James Read
dc6f6c2896 feature: Kill support in the UI (#297)
* feature: Kill command API (wip)

* feature: Kill support in the UI
2024-04-27 21:51:45 +00:00
James Read
a783fc8cd4 feature: Arguments are now also passed as ENV variables (#296) 2024-04-26 23:03:17 +00:00
James Read
6a16a7c6c2 fmt: onfileindir (#295) 2024-04-26 23:02:40 +00:00
James Read
ceb215a6dc bugfix: #272 if the dialog is already open, close it (#294) 2024-04-26 23:02:17 +00:00
James Read
d6cb634824 feature: Extra file info using onfileindir (#293) 2024-04-26 22:05:28 +00:00
James Read
500419307b Nullable args (#292)
* feature: Argument values can be null be default. Use RejectNull to change.

* feature: Argument values can be null be default. Use RejectNull to change.
2024-04-26 17:36:33 +00:00
James Read
12cf0013e2 feature: extra args for fileindir (#291) 2024-04-25 23:32:58 +00:00
James Read
318d4fe0d0 cicd: Reduce test flake and add lots more debugging info (#290) 2024-04-24 15:22:15 +00:00
James Read
a3aee3603f More screenshots link (#289)
* More screenshots link

* bugfix: wip

* cicd: Test flakey tests

* cicd: more tests

* cicd: more tests

* cicd: Try a 10s timeout

* cicd: Better debugging of process start / stop

* cicd: Better debugging of process start / stop

* cicd: Possibly unflaky tests
2024-04-23 20:54:54 +00:00
jamesread
7c6f36c600 doc: Update images 2024-04-22 23:34:36 +01:00
James Read
dde8a9cbb6 cicd: More test coverage that would have caught recent issues (#287)
* cicd: More test coverage that would have caught recent issues

* bugfix: test flake

* bugfix: test flake

* cicd: Reduce test flake

* cicd: more test coverage

* depbump: testing packages
2024-04-22 21:40:48 +00:00
James Read
dfca712cb1 bugfix: Update icon on buttons (#288) 2024-04-22 19:02:59 +00:00
James Read
5057ba2e1c bugfix: Start filewatcher on different goroutines (#286) 2024-04-21 19:51:49 +00:00
jamesread
8f1dfffa49 fmt: Clean up log statement 2024-04-21 20:12:47 +01:00
James Read
6991724258 bugfix: Unexpected message #283 (#284) 2024-04-20 19:58:38 +00:00
James Read
09e1de06ce bugfix: Dont rename logo filenames (#282) 2024-04-19 23:27:13 +00:00
James Read
5a644b0856 bugfix: The web interface now refreshes when the config file is changed! (#281) 2024-04-19 23:11:40 +00:00
James Read
86b2187236 cicd: Increase coverage (#280) 2024-04-19 21:56:45 +00:00
James Read
fafacfeafb bugfix: Move all inotify to filehelper (#279) 2024-04-19 21:56:21 +00:00
James Read
362019738d feature: #267 add jq to containers (#278) 2024-04-19 16:16:23 +00:00
dependabot[bot]
1a0ba6c6b1 build(deps): bump golang.org/x/net from 0.22.0 to 0.23.0 (#277)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 16:16:09 +00:00
jamesread
4c2cc5da1c bugfix: Remove debug log left in the code 2024-04-19 00:58:05 +01:00
James Read
30e2aa141b feature: Rate limit for actions (#76) (#275) 2024-04-18 23:51:59 +00:00
dependabot[bot]
3768a57eaa build(deps): bump github.com/docker/docker (#274)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.0+incompatible to 26.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.0...v26.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 23:49:41 +00:00
James Read
db5de9be97 feature: #135 Permissions for logs (#273) 2024-04-18 20:52:04 +00:00
James Read
f60ab6ce85 feature: Show username in header (#270) 2024-04-15 00:09:55 +00:00
James Read
43c48aef17 feature: Renamed authJwtSecret to authJwtHmacSecret (#271)
* feature: Renamed authJwtSecret to authJwtHmacSecret

* feature: rename authJwtSecret
2024-04-15 00:01:30 +00:00
James Read
8341b1d6d0 feature: Huge JWT improvements for CloudFlare, etc (#269) 2024-04-14 22:59:45 +00:00
jamesread
dd11961a11 bugfix: custom-webui prefix 2024-04-12 22:07:06 +01:00
James Read
3de819a0e9 feature: add prometheus metrics (#268)
* feature: add prometheus metrics

* bugfix: ixed issue with path
2024-04-11 23:23:40 +00:00
James Read
0b546eaeb5 Improve screenshots visibility 2024-04-11 11:13:10 +01:00
jamesread
558e1819bf doc: Updated mobile screenshot 2024-04-11 10:20:03 +01:00
James Read
9d6afa2fe7 doc: typo #266 (#267) 2024-04-11 06:23:24 +00:00
James Read
5f3a967515 depbump: 2024-04 (#265)
* depbump: 2024-04

* go mod tidy
2024-04-10 23:03:25 +00:00
jamesread
179f1be19a bugfix: Null JWT verifier 2024-04-11 00:04:34 +01:00
James Read
f13a5c070a feature: JWT Validation wip for Cloudflare (#264) 2024-04-10 22:14:33 +00:00
James Read
9476d052b6 cicd: Dont build snapshot on tag (#263) 2024-04-10 20:14:18 +00:00
James Read
1d446ace04 custom-webui and faster theme loading (#262)
* feature: custom-webui and faster theme loading

* feature: custom-webui and faster theme loading
2024-04-09 08:26:18 +00:00
jamesread
3a8d8706a6 bugfix: remove underline from sidebar links 2024-04-04 20:37:25 +01:00
James Read
910418925a bugfix: dark mode headr (#261) 2024-04-02 22:57:54 +00:00
James Read
1b539df2aa bugfix: rounded buttons for folders (#260) 2024-04-02 22:41:59 +00:00
James Read
f5794e57ee bugfix: sidebar nav wont tab when hidden (#259) 2024-04-02 22:40:46 +00:00
James Read
7dd1d0a7fc Markup optimisations (#258)
* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* bugfix: Broken test

* feature: Cleanup markup for accessibility
2024-04-02 21:58:23 +00:00
James Read
ce670cf58c Bug ignored signals (#257)
* bugfix: Signals, like SIGKILL etc were ignored. Now we capture them and add the err to stderr

* bugfix: Signals, like SIGKILL etc were ignored. Now we capture them and add the err to stderr
2024-04-01 20:30:34 +00:00
jamesread
c4d1a2a105 cicd: Bug in dockerfile.arm64 2024-03-24 23:06:37 +00:00
James Read
709223bd46 feature: Removed unimplemented CSS rules from config, but added ID for buttons to allow for easy theming (#255) 2024-03-24 22:35:19 +00:00
jamesread
19b4340e18 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-03-24 22:20:58 +00:00
jamesread
a5a1c64dcb bugfix: Entity buttons didnt support popupOnStart 2024-03-24 22:20:39 +00:00
dependabot[bot]
2b7bdffe41 build(deps): bump follow-redirects in /integration-tests (#248)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-24 21:32:11 +00:00
dependabot[bot]
a2df96354e build(deps): bump github.com/docker/docker (#249)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.3+incompatible to 25.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.3...v25.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-24 21:31:58 +00:00
James Read
8b49eeff98 bugfix: Regex validation client side was not running (#254) 2024-03-24 21:31:23 +00:00
James Read
a8c4db197d Bug webui search order (#253)
* bugfix: Search order of webui directories was not deterministic

* bugfix: webui dir search order was not deterministic

* bugfix: webui dir search order was not deterministic

* bugfix: webui dir search order was not deterministic
2024-03-23 22:14:08 +00:00
James Read
1319f314ff Argument suggestions (#252)
* feature: Suggestions for inputs

* feature: #103 Completed suggestions support
2024-03-23 21:52:53 +00:00
James Read
781abaaf40 feature: Entitiys in dropdowns #247 (#251) 2024-03-22 23:49:41 +00:00
James Read
744debc00a feature: add apprise (#250) 2024-03-22 19:00:30 +00:00
James Read
77321c1bcd cicd: Try {configdir}/var/entities/ when looking for entity files as well (#246) 2024-03-15 10:07:51 +00:00
James Read
c97fd60c25 feature: #56 custom tegex for arguments (#244) 2024-03-08 22:17:10 +00:00
James Read
2fb40ff443 feature: Exec on Calendar file support (#243)
* feature: Exec on calendar file

* feature: support for datetime args

* feature: Schedule commands based on seconds
2024-03-08 20:31:51 +00:00
James Read
a992ef84f5 doc: YouTube Banner (#242) 2024-03-07 17:26:15 +00:00
James Read
8fce4c79b6 Update README.md 2024-03-07 17:11:33 +00:00
jamesread
7e4aa9ebbe doc: Updated demo video 2024-03-06 08:27:54 +00:00
James Read
522e5bb129 cicd: Flakey test on dropdowns (#241) 2024-03-05 17:23:17 +00:00
James Read
1a97836dc3 feature: erm, added support 200,000 icons via iconify (#240) 2024-03-05 16:59:46 +00:00
jamesread
e1bc9276bc doc: Add rounded screenshot 2024-03-05 16:10:43 +00:00
James Read
555b6929e4 feature: Rounded buttons... because apparently they look modern?! (#239)
* feature: Rounded buttons... because apparently they look modern?!

* bugfix: Logo in readme
2024-03-05 14:53:50 +00:00
jamesread
ee4d61e476 bugfix: Logo in readme 2024-03-05 14:48:48 +00:00
James Read
f15235d120 Msot recent execution output on the dashboard. (#238)
* feature: Support for most recent action output on dashboard

* feature: Support for most recent action output on dashboard
2024-03-04 23:33:50 +00:00
jamesread
471d5726f6 bugfix: Switch to first dashboard if actions page is empty 2024-03-04 23:37:53 +00:00
jamesread
e344530fc0 bugfix: Switch to first dashboard if actions page is empty 2024-03-04 22:47:45 +00:00
jamesread
29fe38eff4 bugfix: Switch to first dashboard if actions page is empty 2024-03-03 23:09:15 +00:00
jamesread
25e643371e Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-02-29 23:21:16 +00:00
jamesread
32cb8dd873 feature: entity filepaths are relative to the config dir 2024-02-29 23:20:48 +00:00
James Read
12cc61fba5 feature: Nicer container dashboard example in the default config (#235) 2024-02-29 21:27:54 +00:00
James Read
fe40731df3 bugfix: PageTitle works again (#236) 2024-02-29 21:27:34 +00:00
jamesread
7464ca5543 fmt: typo in "dashboards" 2024-02-29 16:28:04 +00:00
jamesread
ce83521429 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2024-02-28 23:18:40 +00:00
jamesread
27ab530ba6 bugfix: StartAction responds with used tracking ID, not necessarily the requested tracking ID 2024-02-28 23:18:31 +00:00
James Read
843121f5fd bugfix: #233 switch sidebar to 100dvh (#234) 2024-02-28 21:32:44 +00:00
jamesread
d26c469107 bugfix: #233 switch sidebar to 100dvh 2024-02-28 21:27:29 +00:00
James Read
97453260eb bugfix: #230 Set public-url in parcel to . (#231) 2024-02-28 17:40:43 +00:00
jamesread
06b85c5769 cicd: I am tired and cannot type it seems 2024-02-27 01:18:28 +00:00
jamesread
5bb21031ac cicd: extra files for all ghe dockers 2024-02-27 01:09:13 +00:00
jamesread
256d6139b7 cicd: Add extra files needed for docker 2024-02-27 00:24:25 +00:00
jamesread
aa342047ed cicd: Dont run the docker ps in default config 2024-02-27 00:10:07 +00:00
jamesread
ea663f8286 cicd: Vagrant, need /opt/OliveTin-configs created 2024-02-25 23:04:31 +00:00
jamesread
e953dfb017 fmt: Ansible -> Automation, robot icon 2024-02-25 22:45:12 +00:00
jamesread
54170f3da6 feature: Better default config file, restart icons 2024-02-25 22:39:29 +00:00
jamesread
77ec2fea63 feature: sosreport in the browser outputs text/plain 2024-02-25 21:10:47 +00:00
jamesread
83beab4c92 bugfix: onExecutionFinished ignores actions it cannot find 2024-02-25 00:49:17 +00:00
jamesread
29b6d12454 feature: Trigger another action after first action completes 2024-02-25 00:29:55 +00:00
jamesread
866a38f286 bugfix: All execution tracking IDs must now be unique 2024-02-25 00:11:35 +00:00
jamesread
0ec2e7069b bugfix: All previous entity files are removed when file is updated 2024-02-25 00:01:51 +00:00
jamesread
f3934b1906 bugfix: Default path for entities files 2024-02-24 22:28:52 +00:00
jamesread
fb2bb63d15 feature: comfig file provided with docker volume, upgrade to f38 2024-02-24 07:27:54 +00:00
jamesread
58cc04298f feature: Popup execution dialog from logs 2024-02-23 23:52:41 +00:00
jamesread
42535feadf feature: Hide actions in sidebar if it is empty 2024-02-23 22:43:52 +00:00
jamesread
baa690ffc2 feature: include entities in packages 2024-02-23 22:19:45 +00:00
jamesread
d13f0a7acf fmt: regex raw string 2024-02-23 21:36:28 +00:00
jamesread
b747199528 fmt: Removed uneeded assign 2024-02-23 21:35:55 +00:00
jamesread
6a1af44aa0 fmt: actionName -> actionTitle 2024-02-23 21:30:35 +00:00
jamesread
6da050e3b9 bugfix: Allow static IDs 2024-02-23 21:30:13 +00:00
jamesread
b8f23ce80c cicd: fix broken test, hopefully 2024-02-23 17:19:36 +00:00
jamesread
865bef532a bugfix: Titles on every button 2024-02-23 16:55:09 +00:00
jamesread
6fb158190d cicd: make grpc is not working 2024-02-23 16:27:31 +00:00
jamesread
0e3f9c8ceb bugfix: #192 repeating logs 2024-02-23 16:09:58 +00:00
jamesread
4dba6fd0f9 depbump: everything 2024-02-23 00:33:02 +00:00
jamesread
fbbf168e88 cicd: fix tests 2024-02-23 00:08:50 +00:00
jamesread
2cd739c3b4 cicd: make grpc for codeql 2024-02-22 23:57:50 +00:00
jamesread
a8e770726a bugfix: unittests 2024-02-22 23:42:40 +00:00
jamesread
0c5a99cc03 bugfix: Use better hash algo 2024-02-22 23:31:13 +00:00
jamesread
2dee246593 bugfix: sidebar fixed 2024-02-22 23:22:21 +00:00
jamesread
381bf59fbd feature: The mega dashboards & entities commit. 2024-02-22 23:16:43 +00:00
James Read
fddf83f27d https everywhere 2024-02-10 12:23:49 +00:00
James Read
3d3e19e26a Create devskim.yml (#229) 2024-02-10 10:44:39 +00:00
jamesread
c082a5438a cicd: wip 2024-02-09 23:47:27 +00:00
jamesread
5adab1091f cicd: Utility to get the latest snapshot 2024-02-09 23:01:24 +00:00
jamesread
290a2ec91b cicd: upgrade codeql 2024-02-09 22:38:18 +00:00
jamesread
9ebeabac51 cicd: update upload-artifact 2024-02-09 22:27:59 +00:00
jamesread
b5e2c8d6b8 cicd: Include all of webui/* in packages now it is processed by parcel 2024-02-09 22:09:13 +00:00
jamesread
a482b6a3c2 cicd: Upgrade goreleaser, fix deprecations 2024-02-09 22:08:45 +00:00
jamesread
f348de6a03 cicd: Need to build webui-dist before integration tests 2024-02-09 21:57:10 +00:00
jamesread
8df8978516 cicd: upgrade actions 2024-02-09 21:22:10 +00:00
jamesread
086d8fd21c feature: HTML/JS/CSS minifiction and cache busting 2024-02-09 17:01:39 +00:00
jamesread
7dce77adcf cicd: These are just testing certs, but they do not need to be in git 2024-02-09 16:17:42 +00:00
James Read
917a0469d8 Proxy integration tests (#228)
* cicd: Proxies included in integration testing

* cicd: proxy configs for integration testing
2024-02-09 16:12:25 +00:00
James Read
c12431d8a3 feature: popupOnStart allows for many feedback options when actions are started (#227) 2024-02-08 21:54:08 +00:00
jamesread
dc0cf33d37 feature: center align the fieldset titles 2024-02-07 10:58:49 +00:00
jamesread
15d332012f feature: Buttons are now fixed-width, so align to center 2024-02-07 10:53:46 +00:00
jamesread
99460beafd feature: box icon for containers 2024-02-07 10:52:46 +00:00
James Read
5b0cfb5c33 feature: entities wip (#226) 2024-02-07 09:36:57 +00:00
James Read
759e747f54 feature: readyz API endpoint support (#225) 2024-02-07 09:36:50 +00:00
James Read
6892a679ee feature: Dashboards, at long last (#224)
* feature: Dashboards, at long last

* fmt: action button IDs now use hypens. Removed ;
2024-02-07 08:54:22 +00:00
jamesread
1b13a2bc4b cicd: setup-go v5 2024-02-07 08:47:48 +00:00
jamesread
63e8e10a6d cicd: checkout v4 2024-02-07 08:47:00 +00:00
James Read
0615a7e353 feature: #202 Cron seconds support optional. Example in default config. (#223) 2024-02-02 21:58:30 +00:00
James Read
16acf9db91 bugfix: Execution dialog would only show first result (#222)
* bugfix: execution-dialog

* bugfix: execution-dialog
2024-02-02 21:22:29 +00:00
James Read
0c19ba59d2 Markup improvements (#220)
* bugfix: various markup improvements, accessibility

* bugfix: various markup improvements, accessibility
2024-01-27 23:43:30 +00:00
James Read
143528d919 bugfix: #216 - fix scroll in chrome (#219) 2024-01-27 22:09:12 +00:00
dependabot[bot]
07cdf378f6 build(deps): bump follow-redirects in /integration-tests (#214)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-11 08:35:50 +00:00
James Read
f559a2f9c9 feature: #69 - Add action confirmation (#208)
* feature: #69 - Add action confirmation

* fmt: style
2023-12-28 23:50:39 +00:00
James Read
6b3e9e4676 feature: clock and ashtonished emoji (#207) 2023-12-28 23:48:27 +00:00
James Read
3f72b7cc0d bugfix: Argument form on smaller screens (#209) 2023-12-28 23:47:02 +00:00
James Read
4ce5b0e645 cicd: Improve tests (#205)
* cicd: make it easier to grab snapshot builds

* cicd: Better support for running tests against VMs

* Update multipleDropdowns.js
2023-12-28 22:04:47 +00:00
James Read
e92ab8d741 #201 - startActionByAlias should be HTTP GET (#206) 2023-12-28 20:34:55 +00:00
jamesread
6b0e414932 More debug info for auth 2023-12-21 01:26:17 +00:00
jamesread
00927f3ba3 Revert "ACL check was broken, addToEveryAction should come first (#204)"
This reverts commit b0faecfa75.
2023-12-21 01:22:18 +00:00
James Read
b0faecfa75 ACL check was broken, addToEveryAction should come first (#204) 2023-12-21 00:07:40 +00:00
James Read
c15449f99a #197 Dialog has min-width, but should be max-width (#203) 2023-12-20 22:07:56 +00:00
dependabot[bot]
68f04c0912 build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#199)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 13:22:07 +00:00
James Read
c3c010443f feature: Better UI for execution buttons (#195) 2023-12-17 04:19:48 +00:00
dependabot[bot]
15c8abf3d6 build(deps): bump axios and wait-on in /integration-tests (#196)
Bumps [axios](https://github.com/axios/axios) to 1.6.2 and updates ancestor dependency [wait-on](https://github.com/jeffbski/wait-on). These dependencies need to be updated together.


Updates `axios` from 0.27.2 to 1.6.2
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v1.6.2)

Updates `wait-on` from 7.0.1 to 7.2.0
- [Release notes](https://github.com/jeffbski/wait-on/releases)
- [Commits](https://github.com/jeffbski/wait-on/compare/v7.0.1...v7.2.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
- dependency-name: wait-on
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 11:53:11 +00:00
James Read
d0f74c1ab7 feature: Additional APIs for StartAction (#188) 2023-10-31 18:17:35 +00:00
dependabot[bot]
ca921c1890 build(deps): bump github.com/docker/docker (#186)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.5+incompatible to 24.0.7+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.5...v24.0.7)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 10:05:37 +00:00
jamesread
3b60bbce0a feature: Reduce websocket logging 2023-10-25 23:03:34 +01:00
James Read
8f6b384fe6 Notification support (#183)
* feature: #158 shellAfterComplete support for notifications

* fmt: reduce cyclo
2023-10-25 21:32:18 +00:00
dependabot[bot]
4d04264caa build(deps): bump google.golang.org/grpc from 1.57.0 to 1.57.1 (#181)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.57.0 to 1.57.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.57.0...v1.57.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 21:31:31 +00:00
James Read
912fd8089e Feature search logs (#182)
* feature: search logs wip

* feature: Log search
2023-10-25 21:29:41 +00:00
James Read
5739091773 feature: Add actionTitle to blocked log messages (#179) 2023-10-25 19:38:27 +00:00
James Read
a09c278585 bugfix: #172 Recent UI changes totally broke dark mode (#180) 2023-10-25 19:37:43 +00:00
jamesread
a7fb49a11b feature: Tidy up error display, and refresh loop 2023-10-24 12:33:32 +01:00
James Read
3db8ae53b5 bugfix: #173 Websocket fixes 1) The upgrader was refusing reverse proxies, 2) The upgrader was "listening" on /, not /websocket (#177) 2023-10-24 04:48:16 +00:00
James Read
311f9a1d00 feature: Reduce debug log spam in ACL/getDashboardComponents (#176) 2023-10-24 04:43:38 +00:00
jamesread
50204f8180 feature: #175 Print verison with --version 2023-10-23 15:59:38 +01:00
jamesread
d639a802dc cicd: Fix syntax issue with setting date 2023-10-12 23:35:37 +01:00
jamesread
f41eafe3bd cicd: Fix date in artifact name 2023-10-12 23:22:42 +01:00
dependabot[bot]
8b080eb3cc build(deps): bump golang.org/x/net from 0.14.0 to 0.17.0 (#171)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Read <contact@jread.com>
2023-10-12 21:30:14 +00:00
jamesread
268d8a3a90 Merge branch 'main' of ssh://github.com/OliveTin/OliveTin 2023-10-12 09:23:58 +01:00
jamesread
44d6c40c27 cicd: Try to add date to integration test artifacts - easier to sort when downloaded 2023-10-11 22:03:40 +01:00
293 changed files with 34864 additions and 12213 deletions

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python3
import sys
commitmsg = ""
with open('.git/COMMIT_EDITMSG', mode='r') as f:
commitmsg = f.readline().strip()
print("Commit message is: " + commitmsg)
ALLOWED_COMMIT_TYPES = [
"cicd",
"test",
"refactor",
"depbump",
"typo",
"fmt",
"doc",
"bugfix",
"security",
"feature",
]
for allowedType in ALLOWED_COMMIT_TYPES:
if commitmsg.startswith(allowedType + ":"):
print("Allowing commit type: ", allowedType)
sys.exit(0)
print("Commit message should start with commit type. One of: ", ", ".join(ALLOWED_COMMIT_TYPES))
sys.exit(1)

View File

@@ -2,7 +2,9 @@
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
type: bug
labels:
- "waiting-on-developer"
assignees: ''
---

View File

@@ -2,7 +2,9 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
type: feature
labels:
- "waiting-on-developer"
assignees: ''
---

View File

@@ -2,7 +2,9 @@
name: Support request
about: Need some help? Got an error message?
title: ""
labels: support
type: support
labels:
- "waiting-on-developer"
assignees: ''
---

View File

@@ -13,10 +13,14 @@ Helpful information to understand the project can be found here: [CONTRIBUTING](
# Checklist
Please put a X in the boxes as evidence of reading through the checklist.
- [ ] I have read the [CONTRIBUTORS](CONTRIBUTORS.adoc) guide
- [ ] I considered the "3 line" suggestion.
- [ ] I followed the "1 logical change" rule.
- [ ] I have forked the project, and raised this PR on a feature branch.
- [ ] `make githooks` has been run, and my git commit message was accepted by the git hook.
- [ ] `make daemon-compile` runs without any issues.
- [ ] `make daemon-codestyle` runs without any issues.
- [ ] `make daemon-unittests` runs without any issues.
- [ ] `make webui-codestyle` runs without any issues.
- [ ] I ran the `pre-commit` hooks, and my commit message was validated.
- [ ] `make -wC service compile` runs without any issues.
- [ ] `make -wC service codestyle` runs without any issues.
- [ ] `make -wC service unittests` runs without any issues.
- [ ] `make -wC webui codestyle` runs without any issues.
- [ ] `make -w it` runs without any issues.
- [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.

82
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
version: 2
updates:
# npm updates for frontend - targeting "next" branch
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
# npm updates for frontend - targeting "release/2k" branch
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "weekly"
target-branch: "release/2k"
open-pull-requests-limit: 10
# npm updates for integration-tests - targeting "next" branch
- package-ecosystem: "npm"
directory: "/integration-tests"
schedule:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
# npm updates for integration-tests - targeting "release/2k" branch
- package-ecosystem: "npm"
directory: "/integration-tests"
schedule:
interval: "weekly"
target-branch: "release/2k"
open-pull-requests-limit: 10
# Go modules updates for service - targeting "next" branch
- package-ecosystem: "gomod"
directory: "/service"
schedule:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
# Go modules updates for service - targeting "release/2k" branch
- package-ecosystem: "gomod"
directory: "/service"
schedule:
interval: "weekly"
target-branch: "release/2k"
open-pull-requests-limit: 10
# Go modules updates for lang - targeting "next" branch
- package-ecosystem: "gomod"
directory: "/lang"
schedule:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
# Go modules updates for lang - targeting "release/2k" branch
- package-ecosystem: "gomod"
directory: "/lang"
schedule:
interval: "weekly"
target-branch: "release/2k"
open-pull-requests-limit: 10
# Docker updates - targeting "next" branch
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
# Docker updates - targeting "release/2k" branch
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
target-branch: "release/2k"
open-pull-requests-limit: 10

106
.github/workflows/build-and-release.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
---
name: "Build & Release pipeline"
on:
pull_request:
workflow_dispatch:
push:
tags:
- '*'
branches:
- main
- next
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:latest
platforms: arm64,arm
- name: Setup node
uses: actions/setup-node@v4
with:
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'service/go.mod'
cache: true
cache-dependency-path: 'service/go.mod'
- name: Print go version
run: go version
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_KEY }}
- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.CONTAINER_TOKEN }}
- name: get date
run: |
echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
- name: make webui
run: make -w webui-dist
- name: unit tests
run: make -w service-unittests
- name: build service
run: make -w service
- name: integration tests
run: cd integration-tests && make -w
- name: Archive integration tests
uses: actions/upload-artifact@v4.3.1
if: always()
with:
name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}"
path: |
integration-tests
!integration-tests/node_modules
- name: Install goreleaser
uses: goreleaser/goreleaser-action@v6
with:
install-only: true
- name: release
if: github.ref_type != 'tag'
uses: cycjimmy/semantic-release-action@v4
with:
extra_plugins: |
@semantic-release/commit-analyzer
@semantic-release/exec
@semantic-release/git
env:
GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
- name: Archive binaries
uses: actions/upload-artifact@v4.3.1
with:
name: "OliveTin-snapshot-${{ env.DATE }}-${{ github.sha }}"
path: dist/OliveTin*.*

34
.github/workflows/build-buf.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Buf CI
on:
workflow_dispatch:
push:
paths:
- 'proto/**'
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
delete:
permissions:
contents: read
pull-requests: write
jobs:
buf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'service/go.mod'
cache: true
cache-dependency-path: 'service/go.mod'
- uses: bufbuild/buf-action@v1.1.0
with:
token: ${{ secrets.BUF_TOKEN }}
# Change setup_only to true if you only want to set up the Action and not execute other commands.
# Otherwise, you can delete this line--the default is false.
setup_only: false
# Optional GitHub token for API requests. Ensures requests aren't rate limited.
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,67 +0,0 @@
---
name: "Build Snapshot"
on:
- push
- workflow_dispatch
jobs:
build-snapshot:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v2
with:
image: tonistiigi/binfmt:latest
platforms: arm64,arm
- name: Setup node
uses: actions/setup-node@v3
with:
cache: 'npm'
cache-dependency-path: webui/package-lock.json
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '^1.18.0'
cache: true
- name: grpc
run: make -w grpc
- name: make daemon
run: make -w daemon-compile-x64-lin
- name: unit tests
run: make -w daemon-unittests
- name: integration tests
run: cd integration-tests && make -w
- name: goreleaser
uses: goreleaser/goreleaser-action@v4.2.0
with:
distribution: goreleaser
version: latest
args: release --snapshot --clean --parallelism 1 --skip-docker
- name: Archive binaries
uses: actions/upload-artifact@v3.1.0
with:
name: "OliveTin-snapshot-${{ github.sha }}-dist"
path: dist/OliveTin*.*
- name: Archive integration tests
uses: actions/upload-artifact@v3.1.0
with:
name: integration-tests
path: |
integration-tests
!integration-tests/node_modules

View File

@@ -1,74 +0,0 @@
---
name: "Build Tag"
on:
push:
tags:
- '*'
jobs:
build-tag:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v2
with:
image: tonistiigi/binfmt:latest
platforms: arm64,arm
- name: Setup node
uses: actions/setup-node@v3
with:
cache: 'npm'
cache-dependency-path: webui/package-lock.json
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '^1.18.0'
cache: true
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_KEY }}
- name: Login to ghcr
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.CONTAINER_TOKEN }}
- name: grpc
run: make grpc
- name: goreleaser
uses: goreleaser/goreleaser-action@v4.2.0
with:
distribution: goreleaser
version: latest
args: release --clean --parallelism 1
env:
GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
- name: Archive binaries
uses: actions/upload-artifact@v2
with:
name: "OliveTin-${{ github.ref_name }}"
path: dist/OliveTin*.*
- name: Archive integration tests
uses: actions/upload-artifact@v3.1.0
with:
name: integration-tests
path: |
integration-tests
!integration-tests/node_modules

View File

@@ -17,7 +17,7 @@ on:
paths:
- 'cmd/**'
- 'internal/**'
- 'webui/**'
- 'webui.dev/**'
- 'integration-tests/**'
- 'OliveTin.proto'
branches: [main]
@@ -42,13 +42,22 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'service/go.mod'
cache: true
cache-dependency-path: 'service/go.mod'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -6,7 +6,7 @@ on:
paths:
- 'cmd/**'
- 'internal/**'
- 'webui/**'
- 'webui.dev/**'
- 'integration-tests/**'
- 'OliveTin.proto'
@@ -16,19 +16,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: '^1.18.0'
go-version-file: 'service/go.mod'
cache: true
cache-dependency-path: 'service/go.mod'
- name: deps
run: make grpc
- name: Print go version
run: go version
- name: daemon
run: make daemon-codestyle
- name: service
run: make -wC service codestyle
- name: webui
run: make webui-codestyle
- name: frontend
run: make -wC frontend codestyle

34
.github/workflows/devskim.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: DevSkim
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '34 21 * * 2'
jobs:
lint:
name: DevSkim
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
- name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: devskim-results.sarif

View File

@@ -0,0 +1,73 @@
---
name: Issue Responsibility
on:
issue_comment:
types: [created]
jobs:
update-responsibility-labels:
runs-on: ubuntu-latest
steps:
- name: Update responsibility labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const commentAuthor = context.payload.comment.user.login;
const issueNumber = context.payload.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const skipAction = context.payload.comment.body.includes("/skip-responsibility");
if (skipAction) {
core.info("Skipping responsibility label update");
return;
}
const developers = ["jamesread"]
const commenterIsDeveloper = developers.includes(commentAuthor);
const commenterIsUser = !commenterIsDeveloper;
const issueLabels = context.payload.issue.labels.map(label => label.name);
if (issueLabels.includes("waiting-on-developer")) {
if (commenterIsDeveloper) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: "waiting-on-developer",
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: ["waiting-on-requestor"],
});
core.info(`Switched responsibility to user for issue #${issueNumber}`);
}
}
if (issueLabels.includes("waiting-on-requestor")) {
if (commenterIsUser) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: "waiting-on-requestor",
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: ["waiting-on-developer"],
});
core.info(`Switched responsibility to developer for issue #${issueNumber}`);
}
}

22
.gitignore vendored
View File

@@ -1,12 +1,22 @@
webui/node_modules
**/*.swp
**/*.swo
gen/
/OliveTin
/OliveTin.armhf
/OliveTin.exe
reports
service/OliveTin
service/OliveTin.armhf
service/OliveTin.exe
service/reports
releases/
dist/
installation-id.txt
tmp/
frontend/dist/
frontend/node_modules
custom-frontend
integration-tests/screenshots/
.vscode/
webui/
server.log
OliveTin
integration-tests/configs/authRequireGuestsToLogin/sessions.yaml
webui
webui.dev
sessions.yaml

View File

@@ -1,28 +1,32 @@
project_name: OliveTin
version: 2
before:
hooks:
- go mod download
- rm -rf webui/node_modules
- make service-prep
builds:
- env:
- CGO_ENABLED=0
binary: OliveTin
main: main.go
dir: service
goos:
- linux
- windows
- darwin
- freebsd
goarch:
- amd64
- arm64
- arm
- riscv64
goarm:
- 5 # For old RPIs
- 6
- 7
main: cmd/OliveTin/main.go
ignore:
- goos: darwin
goarch: arm # Mac does not work on [32bit] arm
@@ -39,7 +43,7 @@ builds:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Branch }}-{{ .ShortCommit }}"
version_template: "{{ .Branch }}-{{ .ShortCommit }}"
changelog:
sort: asc
groups:
@@ -47,10 +51,10 @@ changelog:
regexp: '^.*?security(\([[:word:]]+\))??!?:.+$'
order: 0
- title: 'Features'
regexp: '^.*?feature(\([[:word:]]+\))??!?:.+$'
regexp: '^.*?feat.*?(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'Bug fixes'
regexp: '^.*?bugfix(\([[:word:]]+\))??!?:.+$'
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 2
- title: Others
order: 999
@@ -59,28 +63,25 @@ changelog:
- '^docs:'
- '^test:'
- '^cicd:'
- '^chore:'
- '^release:'
- '^refactor:'
- '^Merge branch'
archives:
-
format: tar.gz
- formats: tar.gz
files:
- config.yaml
- LICENSE
- README.md
- Dockerfile
- webui
- OliveTin.service
- ./var/
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{ .Arm }}"
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
formats: zip
dockers:
- image_templates:
@@ -95,7 +96,10 @@ dockers:
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Tag}}"
extra_files:
- webui
- webui/
- var/entities/
- config.yaml
- var/helper-actions/
- image_templates:
- "docker.io/jamesread/olivetin:{{ .Tag }}-arm64"
@@ -109,7 +113,10 @@ dockers:
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Tag}}"
extra_files:
- webui
- webui/
- var/entities/
- config.yaml
- var/helper-actions/
docker_manifests:
- name_template: docker.io/jamesread/olivetin:{{ .Version }}
@@ -122,6 +129,12 @@ docker_manifests:
- docker.io/jamesread/olivetin:{{ .Version }}-amd64
- docker.io/jamesread/olivetin:{{ .Version }}-arm64
- name_template: docker.io/jamesread/olivetin:latest-3k
image_templates:
- docker.io/jamesread/olivetin:{{ .Version }}-amd64
- docker.io/jamesread/olivetin:{{ .Version }}-arm64
- name_template: ghcr.io/olivetin/olivetin:{{ .Version }}
image_templates:
- ghcr.io/olivetin/olivetin:{{ .Version }}-amd64
@@ -132,6 +145,12 @@ docker_manifests:
- ghcr.io/olivetin/olivetin:{{ .Version }}-amd64
- ghcr.io/olivetin/olivetin:{{ .Version }}-arm64
- name_template: ghcr.io/olivetin/olivetin:latest-3k
image_templates:
- ghcr.io/olivetin/olivetin:{{ .Version }}-amd64
- ghcr.io/olivetin/olivetin:{{ .Version }}-arm64
nfpms:
- id: default
maintainer: James Read <contact@jread.com>
@@ -147,31 +166,21 @@ nfpms:
file_name_template: '{{ .PackageName }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
contents:
- src: OliveTin.service
- src: var/systemd/OliveTin.service
dst: /etc/systemd/system/OliveTin.service
- src: webui/main.js
- src: webui/
dst: /var/www/olivetin/
- src: webui/index.html
dst: /var/www/olivetin/
- src: webui/*.png
dst: /var/www/olivetin/
- src: webui/*.svg
dst: /var/www/olivetin/
- src: webui/style.css
dst: /var/www/olivetin/
- src: webui/js/
dst: /var/www/olivetin/js/
type: tree
- src: config.yaml
dst: /etc/OliveTin/config.yaml
type: "config|noreplace"
- src: var/entities/*
dst: /etc/OliveTin/entities/
type: "config|noreplace"
- src: var/manpage/OliveTin.1.gz
dst: /usr/share/man/man1/OliveTin.1.gz
@@ -191,28 +200,18 @@ nfpms:
- src: var/openrc/OliveTin
dst: /etc/init.d/OliveTin
- src: webui/main.js
- src: webui/
dst: /var/www/olivetin/
- src: webui/index.html
dst: /var/www/olivetin/
- src: webui/*.png
dst: /var/www/olivetin/
- src: webui/*.svg
dst: /var/www/olivetin/
- src: webui/style.css
dst: /var/www/olivetin/
- src: webui/js/
dst: /var/www/olivetin/js/
type: tree
- src: config.yaml
dst: /etc/OliveTin/config.yaml
type: "config|noreplace"
- src: var/entities/*
dst: /etc/OliveTin/entities/
type: "config|noreplace"
- src: var/manpage/OliveTin.1.gz
dst: /usr/share/man/man1/OliveTin.1.gz
@@ -232,7 +231,7 @@ release:
## Useful links
- [Which download do I need?](https://docs.olivetin.app/choose-package.html)
- [Which download do I need?](https://docs.olivetin.app/install/choose_package.html)
- [Ask for help and chat with others users in the Discord community](https://discord.gg/jhYWWpNJ3v)
Thanks for your interest in OliveTin!

View File

@@ -1,10 +1,19 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
# Alternative semantic commit checker
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.1.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [] # optional: list of Conventional Commits types to allow e.g. [feat, fix, ci, chore, test]

16
.releaserc.yaml Normal file
View File

@@ -0,0 +1,16 @@
---
branches:
- name: main
# range: '3000.x.x'
# - name: release/2k
# range: '>=2000.0.0 <3000.0.0'
plugins:
- '@semantic-release/commit-analyzer'
- '@semantic-release/git'
- - "@semantic-release/exec"
- publishCmd: |
goreleaser release --clean --timeout 60m
tagFormat: '${version}'

69
AGENTS.md Normal file
View File

@@ -0,0 +1,69 @@
## OliveTin Agent Guide
This document helps AI agents contribute effectively to OliveTin.
If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
### Project Overview
- **Service (Go)**: `service/` with business logic under `service/internal/*`
- API (Connect RPC): `service/internal/api`
- Command execution: `service/internal/executor`
- HTTP frontends/proxy: `service/internal/httpservers`
- Config/types/entities: `service/internal/config`, `service/internal/entities`
- **Frontend (Vue 3)**: `frontend/` (served by the service)
- **Integration tests**: `integration-tests/`
- **Protos/Generated**: `proto/`, `service/gen/...`
### How to Run
- Run the server (dev):
- From repo root: `go run ./service`
- Unit tests (Go):
- From repo root: `cd service && make unittests`
- Integration tests (Mocha + Selenium):
- Single test: `cd integration-tests && npx --yes mocha test/general.mjs`
- All tests: `cd integration-tests && npx --yes mocha`
### Test Notes and Gotchas
- The top-level Makefile does not expose `unittests`; use `cd service && make unittests`.
- Connect RPC API must be mounted correctly; in tests, create the handler via `GetNewHandler(ex)` and serve under `/api/`.
- Frontend “ready” state: the app sets `document.body` attribute `loaded-dashboard="<name>"` when loading a dashboard. Integration helpers that test dashboard functionality wait for this before selecting elements. Certain conditions enforcing login will mean that this attribute is not set until a user is logged in.
- Modern UI uses Vue components:
- Action buttons are rendered as `.action-button button`.
- Logs and Diagnostics are Vue router links available via `/logs` and `/diagnostics`.
- Some legacy DOM ids (e.g., `contentActions`) no longer exist; prefer class-based selectors.
- Hidden UI features:
- Footer visibility is controlled by `showFooter` from Init API; tests may assert the footer is absent when config disables it.
### Coding Standards (Go)
- Avoid adding superflous comments that explain what the code is doing. Comments are only to describe business logic decisions.
- Prefer clear, descriptive names; avoid 12 letter identifiers.
- Use early returns and handle edge cases first.
- Do not swallow errors; propagate or log meaningfully.
- Match existing formatting; avoid unrelated reformatting.
- Be safe around nils in executor steps (e.g., guard `req.Binding` and `req.Binding.Action`).
### API and Execution Flow (High-level)
1. Client calls Connect RPC (e.g., `Init`, `GetDashboard`, `StartAction`).
2. API translates requests to `executor.ExecutionRequest` and calls `Executor.ExecRequest`.
3. Executor runs a chain of steps: request binding → concurrency/rate/ACL checks → arg parsing → exec → post-exec → logging/triggering.
4. Logs are stored and can be fetched via `ExecutionStatus`/`GetLogs`.
### Common Tasks
- Add/modify actions: update `config.yaml` and ensure `executor.RebuildActionMap()` is called when needed.
- Adjust dashboard rendering: see `service/internal/api/dashboards.go` and `apiActions.go`.
- Frontend behavior:
- Router: `frontend/resources/vue/router.js`
- Main shell/layout: `frontend/resources/vue/App.vue`
- Action button behavior: `frontend/resources/vue/ActionButton.vue`
### Contributing Checklist
- Review the contributing guidelines at `CONTRIBUTING.adoc`.
- Review the AI guidance in `AI.md`.
- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`.
### Troubleshooting
- API tests failing with content-type errors: ensure Connect handler is served under `/api/` and the client targets that base URL.
- Executor panics: check for nil `Binding/Action` and add guards in step functions.
- Integration timeouts: wait for `loaded-dashboard` and use selectors matching the Vue UI.

32
AI.md Normal file
View File

@@ -0,0 +1,32 @@
# OliveTin's AI Policy
## Runtime:
- [x] The project does not include any AI functionality at runtime.
- [x] No data, usage, or similar is sent to, or analyized by AI.
## Development - Contributions
- [x] The project **does accept** contributions that were written with AI help. **However**:
- The contribution must be attributed to a human username who takes responsibility for the code as if they wrote it themselves.
- AI often generates very unmaintainable code as it gets longer - loads of duplication, very little function re-use amd very poor at following style guides / idiomatic design. All code contributions (AI or not) are scrutinized hard for **maintainability** and **clean merging**. Please follow the CONTRIBUTORS guide.
- AI that helps with short tab completion is generally fine.
- AI that writes lots of new code across lots of files, or makes lots of superfluous changes is generally less likely to be accepted.
- Vibe coding is not a suitable way to contribute to this project.
- [x] Contributors should declare when AI has been used to help write contributions in the pull request body message.
- [x] The project uses AI as an **optional** part of the PR process (coderabbitai). Please raise any concerns about usage within the PR.
- [x] Suggestions from coderabbitai can be accepted verbaitem, but ideally it should be the PR author that uses coderabbitai as a guide, who then re-writes the contribution.
- [x] Maintainers are the only agents permitted to accept merges.
## Development - Build process
- [x] Linters, code review tools, and others which are enabled by AI are allowed, but cannot be added as part of the standard build process.
## Community
- [x] Only project admins are allowed to run bots in the community discord server (at the time of writing, the current bot, Japella, is not AI-enabled).
- [x] Support is currently not provided by AI.
## Training
- [x] You may use the OliveTin code base, documentation and repositories to train AI, but obviously usernames and personally identifiable information may not be stored.

View File

@@ -11,6 +11,14 @@ Ideas may be discussed, purely on their merits and issues. Our Code of Conduct
discussion throughout the whole process. This project respects the
link:https://www.kernel.org/doc/html/latest/process/code-of-conduct.html[Linux Kernel code of conduct].
== Suggestion: More than 3 lines - talk to someone first
If you're planning on making a change that's more than a 3 lines, please talk to someone first (raising a GitHub issue is the best way to do that). This is so that you don't waste your time on something that might not be accepted. It's also a good way to get some feedback on your idea and make sure you're on the right track.
== Rule: A PR should be one logical change
Please try to keep your pull requests small and focused. It's almost impossible to review PRs that change lots of files for lots of different reasons. If you have a big change, it's probably best to break it down into smaller, more manageable chunks, otherwise it's likely to be rejected.
== If you're not sure, ask!
Don't be afraid to ask for advice before working on a
@@ -22,17 +30,25 @@ the general direction and roadmap of this project without asking.
The preferred way to communicate is probably via Discord or GitHub issues.
=== Dev environment setup and clean build - Fedora
== Dev environment setup and clean build
```
dnf install git go protobuf-compiler make -y
# Step1: setup compile env
# - Fedora
dnf install git go protobuf-compiler make pre-commit -y
# - Windows with chocolatey
choco install git go protoc make python nodejs-lts -y && pip install pre-commit
# Step2: clone and setup repo
git clone https://github.com/OliveTin/OliveTin.git
cd OliveTin
pre-commit install
# `make grpc` will also run `make go-tools`, which installs "buf". This binary
# Step3: compile binary for current dev env (OS, ARCH)
# `make proto` will also run `make go-tools`, which installs "buf". This binary
# will be put in your GOPATH/bin/, which should be on your path. buf is used to
# generate the protobuf / grpc stubs.
make grpc
# generate the protobuf / Connect RPC stubs.
make proto
make
./OliveTin
```
@@ -42,7 +58,7 @@ make
The project layout is reasonably straightforward;
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
* The API is defined in protobuf+grpc - you will need to `make grpc`.
* The API is defined in protobuf+Connect RPC - you will need to `make proto`.
* The Go daemon is built from the `cmd` and `internal` directories mostly.
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.

View File

@@ -1,24 +1,45 @@
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:36-x86_64
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:42-x86_64 AS olivetin-tmputils
RUN microdnf -y install dnf-plugins-core && \
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:42-x86_64
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
LABEL org.opencontainers.image.title=OliveTin
LABEL org.opencontainers.image.title OliveTin
RUN mkdir -p /config /var/www/olivetin \
&& microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
RUN mkdir -p /config /config/entities/ /var/www/olivetin \
&& \
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
iputils \
openssh-clients \
kubernetes-client \
shadow-utils \
docker \
apprise \
jq \
git \
&& microdnf clean all
COPY --from=olivetin-tmputils \
/usr/bin/docker \
/usr/bin/docker
COPY --from=olivetin-tmputils \
/usr/libexec/docker/cli-plugins/docker-compose \
/usr/libexec/docker/cli-plugins/docker-compose
RUN useradd --system --create-home olivetin -u 1000
EXPOSE 1337/tcp
COPY config.yaml /config
COPY var/entities/* /config/entities/
VOLUME /config
COPY OliveTin /usr/bin/OliveTin
COPY webui /var/www/olivetin/
COPY var/helper-actions/* /usr/bin/
USER olivetin

View File

@@ -1,23 +1,45 @@
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:36-aarch64
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:42-aarch64 AS olivetin-tmputils
RUN microdnf -y install dnf-plugins-core && \
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:42-aarch64
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
LABEL org.opencontainers.image.title=OliveTin
LABEL org.opencontainers.image.title OliveTin
RUN mkdir -p /config /var/www/olivetin \
RUN mkdir -p /config /config/entities/ /var/www/olivetin \
&& \
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
iputils \
openssh-clients \
kubernetes-client \
shadow-utils \
openssh-clients
apprise \
jq \
git \
&& microdnf clean all
COPY --from=olivetin-tmputils \
/usr/bin/docker \
/usr/bin/docker
COPY --from=olivetin-tmputils \
/usr/libexec/docker/cli-plugins/docker-compose \
/usr/libexec/docker/cli-plugins/docker-compose
RUN useradd --system --create-home olivetin -u 1000
EXPOSE 1337/tcp
COPY config.yaml /config
COPY var/entities/* /config/entities/
VOLUME /config
COPY OliveTin /usr/bin/OliveTin
COPY webui /var/www/olivetin/
COPY var/helper-actions/* /usr/bin/
USER olivetin

View File

@@ -3,7 +3,7 @@ FROM --platform=linux/armhfp registry.fedoraproject.org/fedora-minimal:36-armhfp
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
LABEL org.opencontainers.image.title=OliveTin
RUN mkdir -p /config /var/www/olivetin \
RUN mkdir -p /config /config/entities /var/www/olivetin \
&& \
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
iputils \
@@ -14,10 +14,13 @@ RUN useradd --system --create-home olivetin -u 1000
EXPOSE 1337/tcp
COPY config.yaml /config
COPY var/entities/* /config/entities/
VOLUME /config
COPY OliveTin /usr/bin/OliveTin
COPY webui /var/www/olivetin/
COPY var/helper-actions/* /usr/bin/
USER olivetin

50
Jenkinsfile vendored
View File

@@ -1,50 +0,0 @@
pipeline {
agent any
options {
skipDefaultCheckout(true)
}
stages {
stage ('Pre-Build') {
steps {
cleanWs()
checkout scm
sh 'make go-tools'
}
}
stage('Compile') {
steps {
withEnv(["PATH+GO=/root/go/bin/"]) {
sh 'go env'
sh 'echo $PATH'
sh 'buf generate'
sh 'make daemon-compile'
}
}
}
stage ('Post-Compile') {
parallel {
stage('Codestyle') {
steps {
withEnv(["PATH+GO=/root/go/bin/"]) {
sh 'make daemon-codestyle'
sh 'make webui-codestyle'
}
}
}
stage('UnitTests') {
steps {
withEnv(["PATH+GO=/root/go/bin/"]) {
sh 'make daemon-unittests'
}
}
}
}
}
}
}

View File

@@ -1,46 +1,28 @@
compile: daemon-compile-x64-lin
define delete-files
python -c "import shutil;shutil.rmtree('$(1)', ignore_errors=True)"
endef
daemon-compile-armhf:
GOARCH=arm GOARM=6 go build -o OliveTin.armhf github.com/OliveTin/OliveTin/cmd/OliveTin
service:
$(MAKE) -wC service
daemon-compile-x64-lin:
GOOS=linux go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin
service-prep:
$(MAKE) -wC service prep
daemon-compile-x64-win:
GOOS=windows GOARCH=amd64 go build -o OliveTin.exe github.com/OliveTin/OliveTin/cmd/OliveTin
service-unittests:
$(MAKE) -wC service unittests
daemon-compile: daemon-compile-armhf daemon-compile-x64-lin daemon-compile-x64-win
daemon-codestyle:
go fmt ./...
go vet ./...
gocyclo -over 4 cmd internal
gocritic check ./...
daemon-unittests:
mkdir -p reports
go test ./... -coverprofile reports/unittests.out
go tool cover -html=reports/unittests.out -o reports/unittests.html
githooks:
cp -v .githooks/* .git/hooks/
it:
$(MAKE) -wC integration-tests
go-tools:
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/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
go install "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
go install "google.golang.org/protobuf/cmd/protoc-gen-go"
$(MAKE) -wC service go-tools
grpc: go-tools
buf generate
proto: go-tools
$(MAKE) -wC proto
dist: protoc
dist:
echo "dist noop"
protoc:
protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. -I .:/usr/include/ OliveTin.proto
podman-image:
buildah bud -t olivetin
@@ -62,12 +44,19 @@ devrun: compile
devcontainer: compile podman-image podman-container
webui-codestyle:
cd webui && npm install
cd webui && ./node_modules/.bin/eslint main.js js/*
cd webui && ./node_modules/.bin/stylelint style.css
webui-dist:
$(MAKE) -wC frontend dist
mv frontend/dist webui
clean:
rm -rf dist OliveTin OliveTin.armhf OliveTin.exe reports gen
$(call delete-files,dist)
$(call delete-files,OliveTin)
$(call delete-files,OliveTin.armhf)
$(call delete-files,OliveTin.exe)
$(call delete-files,reports)
$(call delete-files,gen)
.PHONY: grpc
config-tool:
cd service && go run cmd/config-tool/main.go
.PHONY: proto service

View File

@@ -1,171 +0,0 @@
syntax = "proto3";
option go_package = "gen/grpc";
import "google/api/annotations.proto";
message Action {
string id = 1;
string title = 2;
string icon = 3;
bool can_exec = 4;
repeated ActionArgument arguments = 5;
bool popup_on_start = 6;
}
message ActionArgument {
string name = 1;
string title = 2;
string type = 3;
string default_value = 4;
repeated ActionArgumentChoice choices = 5;
string description = 6;
}
message ActionArgumentChoice {
string value = 1;
string title = 2;
}
message Entity {
string title = 1;
string icon = 2;
repeated Action actions = 3;
}
message GetDashboardComponentsResponse {
string title = 1;
repeated Action actions = 2;
repeated Entity entities = 3;
}
message GetDashboardComponentsRequest {}
message StartActionRequest {
string action_name = 1;
repeated StartActionArgument arguments = 2;
string uuid = 3;
}
message StartActionArgument {
string name = 1;
string value = 2;
}
message StartActionResponse {
string execution_uuid = 2;
}
message GetLogsRequest{};
message LogEntry {
string datetime_started = 1;
string action_title = 2;
string stdout = 3;
string stderr = 4;
bool timed_out = 5;
int32 exit_code = 6;
string user = 7;
string user_class = 8;
string action_icon = 9;
repeated string tags = 10;
string execution_uuid = 11;
string datetime_finished = 12;
string uuid = 13;
bool execution_started = 14;
bool execution_finished = 15;
bool blocked = 16;
}
message GetLogsResponse {
repeated LogEntry logs = 1;
}
message ValidateArgumentTypeRequest {
string value = 1;
string type = 2;
}
message ValidateArgumentTypeResponse {
bool valid = 1;
string description = 2;
}
message WatchExecutionRequest {
string execution_uuid = 1;
}
message WatchExecutionUpdate {
string update = 1;
}
message ExecutionStatusRequest {
string execution_uuid = 1;
}
message ExecutionStatusResponse {
LogEntry log_entry = 1;
}
message WhoAmIRequest {}
message WhoAmIResponse {
string authenticated_user = 1;
}
message SosReportRequest {}
message SosReportResponse {
string alert = 1;
}
service OliveTinApiService {
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
option (google.api.http) = {
get: "/api/GetDashboardComponents"
};
}
rpc StartAction(StartActionRequest) returns (StartActionResponse) {
option (google.api.http) = {
post: "/api/StartAction"
body: "*"
};
}
rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
option (google.api.http) = {
post: "/api/ExecutionStatus"
body: "*"
};
}
rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) {
option (google.api.http) = {
get: "/api/GetLogs"
};
}
rpc ValidateArgumentType(ValidateArgumentTypeRequest) returns (ValidateArgumentTypeResponse) {
option (google.api.http) = {
post: "/api/ValidateArgumentType"
body: "*"
};
}
rpc WhoAmI(WhoAmIRequest) returns (WhoAmIResponse) {
option (google.api.http) = {
get: "/api/WhoAmI"
};
}
rpc SosReport(SosReportRequest) returns (SosReportResponse) {
option (google.api.http) = {
get: "/api/sosreport"
};
}
}

104
README.md
View File

@@ -1,15 +1,23 @@
# OliveTin
<div align = "center">
<img alt = "project logo" src = "https://github.com/OliveTin/OliveTin/blob/main/frontend/OliveTinLogo.png" width = "128" />
<h1>OliveTin</h1>
<img alt = "project logo" src = "https://github.com/OliveTin/OliveTin/blob/main/webui/OliveTinLogo.png" align = "right" width = "160px" />
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
[![Maturity Badge](https://img.shields.io/badge/maturity-Production-brightgreen)](#none)
[![Discord](https://img.shields.io/discord/846737624960860180?label=Discord%20Server)](https://discord.gg/jhYWWpNJ3v)
[![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/awesome-selfhosted/awesome-selfhosted#automation)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5050/badge)](https://bestpractices.coreinfrastructure.org/projects/5050)
[![Go Report Card](https://goreportcard.com/badge/github.com/Olivetin/OliveTin)](https://goreportcard.com/report/github.com/OliveTin/OliveTin)
[![Build Snapshot](https://github.com/OliveTin/OliveTin/actions/workflows/build-snapshot.yml/badge.svg)](https://github.com/OliveTin/OliveTin/actions/workflows/build-snapshot.yml)
[OliveTin 2k to 3k upgrade guide](https://docs.olivetin.app/upgrade/2k3k.html)
</div>
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-laptop_framed.png" />
<a href = "#screenshots">More screenshots below</a>
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
## Use cases
@@ -22,14 +30,14 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
**Simplify** complex commands, make them accessible and repeatable;
* eg: Expose complex commands on touchscreen tablets stuck on walls around your house. `wake-on-lan aa:bb:cc:11:22:33`
* eg: Run long running on your servers from your cell phone. `dnf update -y`
* eg: Run long-lived commands on your servers from your cell phone. `dnf update -y`
* eg: Define complex commands with lots of preset arguments, and turn a few arguments into dropdown select boxes. `docker rm {{ container }} && docker create {{ container }} && docker start {{ container }}`
[Join the community on Discord](https://discord.gg/jhYWWpNJ3v) to talk with other users about use cases, or to ask for support in getting started.
## YouTube demo video (6 mins)
## YouTube demo video
[![6 minute demo video](https://img.youtube.com/vi/Ej6NM9rmZtk/0.jpg)](https://www.youtube.com/watch?v=Ej6NM9rmZtk)
[![YouTube demo video](https://raw.githubusercontent.com/OliveTin/OliveTin/main/var/marketing/YouTubeBanner.png)](https://www.youtube.com/watch?v=UBgOfNrzId4)
## Features
@@ -38,76 +46,42 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
* **Dark mode** - for those of you that roll that way.
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretially you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
* **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/gRPC API.
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretically you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
* **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/Connect RPC API.
* **Good amount of unit tests and style checks** - helps potential contributors be consistent, and helps with maintainability.
## Screenshots
Desktop web browser;
![Desktop screenshot](media/screenshotDesktop.png)
<p align = "center">
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-laptop_framed.png" />
</p>
Desktop web browser (dark mode);
![Desktop screenshot](media/screenshotDesktopDark.png)
<p align = "center">
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-darkop_framed.png" />
</p>
Mobile screen size (responsive layout);
![Mobile screenshot](media/screenshotMobile.png)
<p align = "center">
<img alt = "screenshot" src = "https://github.com/OliveTin/OliveTin/blob/main/var/marketing/screenshots/mainpage-phone_framed.png" style = "width: 700px;" />
</p>
## No-Nonsense Software Principles
OliveTin follows these principles:
* **Open Source & Free Software**: following the [Open Source Definition](https://opensource.org/osd) and the [Free Software Definition](https://www.gnu.org/philosophy/free-sw.html). All code and assets are available under the [AGPL-3.0 License](LICENSE).
* **Independent**: No company owns the code or is responsible for the projects' governance.
* **Inclusive**: No "core", "pro", "premium" or "enterprise" version. The only version is the one you can download and run, and it has all the features.
* **Invisible**: No usage tracking, no user tracking, no ads, and no telemetry.
* **Internal**: No internet connection required for any functionality.
## Documentation
All documentation can be found at http://docs.olivetin.app . This includes installation and usage guide, etc.
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
### Quickstart reference for `config.yaml`
This is a quick example of `config.yaml` - but again, lots of documentation for how to write your `config.yaml` can be found at [the documentation site.](https://docs.olivetin.app)
* (Recommended) [Linux package install (.rpm/.deb)](https://docs.olivetin.app/install-linuxpackage.html) install instructions
* [Container (podman/docker)](https://docs.olivetin.app/install-container.html) install instructions
* [Docker compose](https://docs.olivetin.app/install-compose.html) install instructions
* [Helm on Kubernetes](https://docs.olivetin.app/install-helm.html) install instructions
* [Kubernetes (manual)](https://docs.olivetin.app/install-k8s.html) install instructions
* [.tar.gz (manual)](https://docs.olivetin.app/install-targz.html) install instructions
Put this `config.yaml` in `/etc/OliveTin/` if you're running a standard service, or mount it at `/config` if running in a container.
```yaml
# Listen on all addresses available, port 1337
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
# Choose from INFO (default), WARN and DEBUG
logLevel: "INFO"
# Actions (buttons) to show up on the WebUI:
actions:
# Docs: https://docs.olivetin.app/action-container-control.html
- title: Restart Plex
icon: restart
shell: docker restart plex
# This will send 1 ping
# Docs: https://docs.olivetin.app/action-ping.html
- title: Ping host
shell: ping {{ host }} -c {{ count }}
icon: ping
arguments:
- name: host
title: host
type: ascii_identifier
default: example.com
- name: count
title: Count
type: int
default: 1
# Restart http on host "webserver1"
# Docs: https://docs.olivetin.app/action-ssh.html
- title: restart httpd
icon: restart
shell: ssh root@webserver1 'service httpd restart'
```
A full example config can be found at in this repository - [config.yaml](https://github.com/OliveTin/OliveTin/blob/main/config.yaml).
You can find instructions in the docs on how to install as a **Linux package**, **Linux Container**, on **FreeBSD**, **Windows**, **MacOS** and other platforms, too!

View File

@@ -1,19 +0,0 @@
version: v1
plugins:
- name: go
out: gen/grpc/
opt: paths=source_relative
- name: go-grpc
out: gen/grpc/
opt: paths=source_relative,require_unimplemented_servers=false
- name: grpc-gateway
out: gen/grpc/
opt: paths=source_relative
# - name: swagger
# out: reports/swagger
# - name: openapiv2
# out: reports/openapiv2

View File

@@ -1,7 +0,0 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: googleapis
repository: googleapis
commit: e9fcfb66f77242e5b8fd4564d7a01033

View File

@@ -1,126 +0,0 @@
package main
import (
"flag"
log "github.com/sirupsen/logrus"
"github.com/OliveTin/OliveTin/internal/executor"
grpcapi "github.com/OliveTin/OliveTin/internal/grpcapi"
"github.com/OliveTin/OliveTin/internal/installationinfo"
"github.com/OliveTin/OliveTin/internal/oncron"
"github.com/OliveTin/OliveTin/internal/onfileindir"
"github.com/OliveTin/OliveTin/internal/onstartup"
updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck"
"github.com/OliveTin/OliveTin/internal/websocket"
"github.com/OliveTin/OliveTin/internal/httpservers"
config "github.com/OliveTin/OliveTin/internal/config"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"os"
"path"
)
var (
cfg *config.Config
version = "dev"
commit = "nocommit"
date = "nodate"
)
func init() {
log.SetFormatter(&log.TextFormatter{
ForceQuote: true,
DisableTimestamp: true,
})
log.WithFields(log.Fields{
"version": version,
"commit": commit,
"date": date,
}).Info("OliveTin initializing")
log.SetLevel(log.DebugLevel) // Default to debug, to catch cfg issues
var configDir string
flag.StringVar(&configDir, "configdir", ".", "Config directory path")
flag.Parse()
log.WithFields(log.Fields{
"value": configDir,
}).Debugf("Value of -configdir flag")
viper.AutomaticEnv()
viper.SetConfigName("config.yaml")
viper.SetConfigType("yaml")
viper.AddConfigPath(configDir)
viper.AddConfigPath("/config") // For containers.
viper.AddConfigPath("/etc/OliveTin/")
if err := viper.ReadInConfig(); err != nil {
log.Errorf("Config file error at startup. %s", err)
os.Exit(1)
}
cfg = config.DefaultConfig()
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
if e.Op == fsnotify.Write {
log.Info("Config file changed:", e.String())
reloadConfig()
}
})
reloadConfig()
warnIfPuidGuid()
installationinfo.Config = cfg
installationinfo.Build.Version = version
installationinfo.Build.Commit = commit
installationinfo.Build.Date = date
log.Info("Init complete")
}
func warnIfPuidGuid() {
if os.Getenv("PUID") != "" || os.Getenv("PGID") != "" {
log.Warnf("PUID or PGID seem to be set to something, but they are ignored by OliveTin. Please check https://docs.olivetin.app/no-puid-pgid.html")
}
}
func reloadConfig() {
if err := viper.UnmarshalExact(&cfg); err != nil {
log.Errorf("Config unmarshal error %+v", err)
os.Exit(1)
}
cfg.Sanitize()
}
func main() {
configDir := path.Dir(viper.ConfigFileUsed())
log.WithFields(log.Fields{
"configDir": configDir,
}).Infof("OliveTin started")
log.Debugf("Config: %+v", cfg)
executor := executor.DefaultExecutor()
executor.AddListener(websocket.ExecutionListener)
go onstartup.Execute(cfg, executor)
go oncron.Schedule(cfg, executor)
go onfileindir.WatchFilesInDirectory(cfg, executor)
go updatecheck.StartUpdateChecker(version, commit, cfg, configDir)
go grpcapi.Start(cfg, executor)
httpservers.StartServers(cfg)
}

View File

@@ -6,78 +6,383 @@
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
# Choose from INFO (default), WARN and DEBUG
# Docs: https://docs.olivetin.app/advanced_configuration/logs.html
logLevel: "INFO"
# Actions (buttons) to show up on the WebUI:
# Actions are commands that are executed by OliveTin, and normally show up as
# buttons on the WebUI.
#
# Docs: https://docs.olivetin.app/action_execution/create_your_first.html
actions:
# This will run a simple script that you create.
- title: Run backup script
shell: /opt/backupScript.sh
maxConcurrent: 1
icon: backup
- title: date
shell: date
# This will send 1 ping (-c 1)
# Docs: https://docs.olivetin.app/action-ping.html
- title: Ping host
shell: ping {{ host }} -c {{ count }}
icon: ping
timeout: 100
arguments:
- name: host
title: host
type: ascii_identifier
default: example.com
description: The host that you want to ping
- name: count
title: Count
type: int
default: 1
description: How many times to do you want to ping?
# Restart lightdm on host "server1"
# Docs: https://docs.olivetin.app/action-ping.html
- title: restart httpd
icon: restart
shell: ssh root@server1 'service httpd restart'
# OliveTin can run long-running jobs like Ansible playbooks.
# This is the most simple action, it just runs the command and flashes the
# button to indicate status.
#
# For such jobs, you will need to install ansible-playbook on the host where
# you are running OliveTin, or in the container.
# If you are running OliveTin in a container remember to pass through the
# docker socket! https://docs.olivetin.app/solutions/container-control-panel/index.html
- title: Ping the Internet
shell: ping -c 3 1.1.1.1
icon: ping
popupOnStart: execution-dialog-stdout-only
# This uses `popupOnStart: execution-dialog-stdout-only` to simply show just
# the command output.
- title: Check disk space
icon: disk
shell: df -h /media
popupOnStart: execution-dialog-stdout-only
# This uses `popupOnStart: execution-dialog` to show a dialog with more
# information about the command that was run.
- title: check dmesg logs
shell: dmesg | tail
icon: logs
popupOnStart: execution-dialog
# This uses `popupOnStart: execution-button` to display a mini button that
# links to the logs.
#
# You probably want a much longer timeout as well (so that ansible completes).
- title: "Run Ansible Playbook"
icon: "&#x1F1E6"
shell: ansible-playbook -i /etc/hosts /root/myRepo/myPlaybook.yaml
timeout: 120
# You can also rate-limit actions too.
- title: date
shell: date
id: date
timeout: 6
icon: clock
popupOnStart: execution-button
maxRate:
- limit: 3
duration: 5m
# You are not limited to operating system commands, and of course you can run
# your own scripts. Here `maxConcurrent` stops the script running multiple
# times in parallel. There is also a timeout that will kill the command if it
# runs for too long.
- title: Run backup script
shell: /opt/backupScript.sh
shellAfterCompleted: "apprise -t 'Notification: Backup script completed' -b 'The backup script completed with code {{ exitCode}}. The log is: \n {{ output }} '"
maxConcurrent: 1
timeout: 10
icon: backup
popupOnStart: execution-dialog
# When you want to prompt users for input, that is when you should use
# `arguments` - this presents a popup dialog and asks for argument values.
#
# Docs: https://docs.olivetin.app/action_examples/ping.html
- title: Ping host
id: ping_host
shell: ping {{ host }} -c {{ count }}
icon: ping
timeout: 100
popupOnStart: execution-dialog-stdout-only
arguments:
- name: host
title: Host
type: ascii_identifier
default: example.com
description: The host that you want to ping
- name: count
title: Count
type: int
default: 3
description: How many times to do you want to ping?
# OliveTin can control containers - docker is just a command line app.
#
# However, if you are running in a container you will need to do some setup,
# see the docs below.
#
# Docs: https://docs.olivetin.app/action-container-control.html
- title: Restart Docker Container
icon: restart
shell: docker restart {{ container }}
arguments:
- name: container
title: Container name
choices:
- value: plex
- value: traefik
- value: grafana
# Docs: https://docs.olivetin.app/solutions/container-control-panel/index.html
- title: Restart Docker Container
icon: restart
shell: docker restart {{ .CurrentEntity }}
arguments:
- name: container
title: Container name
choices:
- value: plex
- value: traefik
- value: grafana
- title: Slow Script
shell: sleep 3
timeout: 5
icon: "&#x1F971"
# There is a special `confirmation` argument to help against accidental clicks
# on "dangerous" actions.
#
# Docs: https://docs.olivetin.app/args/input_confirmation.html
- title: Delete old backups
icon: ashtonished
shell: rm -rf /opt/oldBackups/
arguments:
- type: html
title: Description
default:
The documentation for this action can be found at <a href = "example.com">example.com</a>.
- type: confirmation
title: Are you sure?!
- title: Broken Script (timeout)
shell: sleep 5
timeout: 5
icon: "&#x1F62A"
# This is an action that runs a script included with OliveTin, that will
# download themes. You will still need to set theme "themeName" in your config.
#
# Docs: https://docs.olivetin.app/reference/reference_themes_for_users.html
- title: Get OliveTin Theme
shell: olivetin-get-theme {{ themeGitRepo }} {{ themeFolderName }}
icon: theme
arguments:
- name: themeGitRepo
title: Theme's Git Repository
description: Find new themes at https://olivetin.app/themes
type: url
- name: themeFolderName
title: Theme's Folder Name
type: ascii_identifier
# Sometimes you want to run actions on other servers - don't overcomplicate
# it, just use SSH! OliveTin includes a helper to make this easier, which is
# entirely optional. You can also setup SSH manually.
#
# Docs: https://docs.olivetin.app/action_examples/ssh-easy.html
# Docs: https://docs.olivetin.app/action_examples/ssh-manual.html
- title: "Setup easy SSH"
icon: ssh
shell: olivetin-setup-easy-ssh
popupOnStart: execution-dialog
# Here's how to use SSH with the "easy" config, to restart a service on
# another server.
#
# Docs: https://docs.olivetin.app/action_examples/ssh-easy.html
# Docs: https://docs.olivetin.app/action_examples/systemd_service.html
- title: Restart httpd on server1
id: restart_httpd
icon: restart
timeout: 1
shell: ssh -F /config/ssh/easy.cfg root@server1 'service httpd restart'
# Lots of people use OliveTin to build web interfaces for their electronics
# projects. It's best to install OliveTin as a native package (eg, .deb), and
# then you can use either a python script or the `gpio` command.
- title: Toggle GPIO light
shell: gpioset gpiochip1 9=1
icon: light
# There are several built-in shortcuts for the `icon` option, but you
# can also just specify any HTML, this includes any unicode character,
# or a <img = "..." /> link to a custom icon.
#
# Docs: https://docs.olivetin.app/action_customization/icons.html
#
# Lots of people use OliveTin to easily execute ansible-playbooks. You
# probably want a much longer timeout as well (so that ansible completes).
#
# Docs: https://docs.olivetin.app/action_examples/ansible.html
- title: "Run Automation Playbook"
icon: '&#129302;'
shell: ansible-playbook -i /etc/hosts /root/myRepo/myPlaybook.yaml
timeout: 120
# The following actions are "dummy" actions, used in a Dashboard. As long as
# you have these referenced in a dashboard, they will not show up in the
# `actions` view.
- title: Ping hypervisor1
shell: echo "hypervisor1 online"
- title: Ping hypervisor2
shell: echo "hypervisor2 online"
- title: Ping hypervisor3
shell: echo "hypervisor3 online"
- title: Ping hypervisor4
shell: echo "hypervisor4 online"
- title: "{{ server.name }} Wake on Lan"
shell: echo "Sending Wake on LAN to {{ server.hostname }}"
icon: <iconify-icon icon="carbon:awake"></iconify-icon>
entity: server
- title: "{{ server.name }} Power Off"
shell: "echo 'Power Off Server: {{ server.hostname }}'"
icon: <iconify-icon icon="carbon:flash-off"></iconify-icon>
entity: server
- title: "{{ server.name }} Print server name"
shell: 'echo "Server name: {{ server.name }}"'
entity: server
- title: Ping All Servers
shell: "echo 'Ping all servers'"
icon: ping
- title: Start {{ .CurrentEntity.Names }}
icon: box
shell: docker start {{ .CurrentEntity.Names }}
entity: container
triggers: ["Update container entity file"]
- title: Stop {{ .CurrentEntity.Names }}
icon: box
shell: docker stop {{ .CurrentEntity.Names }}
entity: container
triggers: ["Update container entity file"]
# Lastly, you can hide actions from the web UI, this is useful for creating
# background helpers that execute only on startup or a cron, for updating
# entity files.
# - title: Update container entity file
# shell: 'docker ps -a --format json > /etc/OliveTin/entities/containers.json'
# hidden: true
# execOnStartup: true
# execOnCron: '*/1 * * * *'
# An entity is something that exists - a "thing", like a VM, or a Container
# is an entity. OliveTin allows you to then dynamically generate actions based
# around these entities.
#
# This is really useful if you want to generate wake on lan or poweroff actions
# for `server` entities, for example.
#
# A very popular use case that entities were designed for was for `container`
# entities - in a similar way you could generate `start`, `stop`, and `restart`
# container actions.
#
# Entities are just loaded fome files on disk, OliveTin will also watch these
# files for updates while OliveTin is running, and update entities.
#
# Entities can have properties defined in those files, and those can be used
# in your configuration as variables. For example; `container.status`,
# or `vm.hostname`.
#
# Docs: https://docs.olivetin.app/entities/intro.html
entities:
# YAML files are the default expected format, so you can use .yml or .yaml,
# or even .txt, as long as the file contains valid a valid yaml LIST, then it
# will load properly.
#
# Docs: https://docs.olivetin.app/entities/intro.html
- file: entities/servers.yaml
name: server
- file: entities/containers.json
name: container
# Dashboards are a way of taking actions from the default "actions" view, and
# organizing them into groups - either into folders, or fieldsets.
#
# The only way to properly use entities, are to use them with a `fieldset` on
# a dashboard.
#
# Docs: https://docs.olivetin.app/dashboards/intro.html
dashboards:
# Top level items are dashboards.
- title: My Servers
contents:
- title: All Servers
type: fieldset
contents:
# The contents of a dashboard will try to look for an action with a
# matching title IF the `contents: ` property is empty.
- title: Ping All Servers
# If you create an item with some "contents:", OliveTin will show that as
# directory.
- title: Hypervisors
contents:
- title: Ping hypervisor1
- title: Ping hypervisor2
- title: More hypervisors
type: directory
contents:
- title: Ping hypervisor3
- title: Ping hypervisor4
# If you specify `type: fieldset` and some `contents`, it will show your
# actions grouped together without a folder.
- type: fieldset
entity: server
title: 'Server: {{ .CurrentEntity.hostname }}'
contents:
# By default OliveTin will look for an action with a matching title
# and put it on the dashboard.
#
# Fieldsets also support `type: display`, which can display arbitary
# text. This is useful for displaying things like a container's state.
- type: display
title: |
Hostname: <strong>{{ server.name }}</strong>
IP Address: <strong>{{ server.ip }}</strong>
# These are the actions (defined above) that we want on the dashboard.
- title: '{{ server.name }} Wake on Lan'
- title: '{{ server.name }} Power Off'
- title: More Options
type: directory
contents:
- title: '{{ server.name }} Print server name'
# This is the second dashboard.
- title: My Containers
contents:
- title: 'Container {{ .CurrentEntity.Names }} ({{ .CurrentEntity.Image }})'
entity: container
type: fieldset
contents:
- type: display
title: |
{{ container.RunningFor }} <br /><br /><strong>{{ container.State }}</strong>
- title: 'Start {{ .CurrentEntity.Names }}'
- title: 'Stop {{ .CurrentEntity.Names }}'
# Security - Authentication
# This setting effectively enables or disables guests.
# If set to "true", then users will have to login to do anything.
authRequireGuestsToLogin: false
# This form of auth is the simplest to setup - just define users and passwords
# in the config. OliveTin also supports header-based auth, OAuth2,
# and JWT authentication which are documented separately.
#
# Docs: https://docs.olivetin.app/security/local.html
#
# How to get a hashed password:
# Docs: https://docs.olivetin.app/security/local.html#_get_a_argon2id_hashed_password
authLocalUsers:
enabled: true
# users:
# - username: alice
# usergroup: admins
# password: "$argon2id$v=19$m=65536,t=4,p=2$puyxA0s555TSFx7hnFLCXA$PyhLGpZtvpMMvc2DgMWkM8OJMKO55euwV5gm//1iwx4"
# Security - Access Control
# Policies affect the whole app (eg: ability to view the log list).
# Docs: https://docs.olivetin.app/security/acl.html
defaultPolicy:
showDiagnostics: true
showLogList: true
# Permissions affect actions (eg: ability to view a specific log).
# Docs: https://docs.olivetin.app/security/acl.html
defaultPermissions:
view: true
exec: true
logs: true
# OliveTin uses access control lists to match up policy and permissions to users.
# Docs: https://docs.olivetin.app/security/acl.html
accessControlLists:
- name: admin_acl
matchUsergroups: ["admins"]
policy:
showDiagnostics: true
permissions:
view: true
exec: true
logs: true
# OliveTin contains many more configuration options not in this default config.
# Check out docs.olivetin.app for a setting if you feel like you're missing something.

View File

@@ -12,4 +12,4 @@
},
"rules": {
}
}
}

1
frontend/.npmrc Normal file
View File

@@ -0,0 +1 @@
fund=false

21
frontend/Makefile Normal file
View File

@@ -0,0 +1,21 @@
define delete-files
python -c "import shutil;shutil.rmtree('$(1)', ignore_errors=True)"
endef
codestyle:
npm install
npx eslint --fix main.js js/*
npx stylelint style.css
clean:
$(call delete-files,dist)
deps:
npm install
build:
npx vite build
dist: deps clean build
.PHONY: codestyle

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

69
frontend/index.html Normal file
View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8" />
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
<meta name = "description" content = "Give safe and simple access to predefined shell commands from a web interface." />
<title>OliveTin</title>
<link rel = "stylesheet" type = "text/css" href = "/theme.css" />
<link rel = "stylesheet" href = "node_modules/@xterm/xterm/css/xterm.css" />
<link rel = "shortcut icon" type = "image/png" href = "OliveTinLogo.png" />
<link rel = "apple-touch-icon" sizes="57x57" href="OliveTinLogo-57px.png" />
<link rel = "apple-touch-icon" sizes="120x120" href="OliveTinLogo-120px.png" />
<link rel = "apple-touch-icon" sizes="180x180" href="OliveTinLogo-180px.png" />
<base href = "/" />
</head>
<body>
<slot id = "app" />
<noscript>
<div class = "error">Sorry, JavaScript is required to use OliveTin.</div>
</noscript>
<dialog title = "Big Error Message" id = "big-error" class = "error padded-content">
</dialog>
<script type = "text/javascript">
const bigErrorDialog = document.getElementById('big-error')
/**
This is the bootstrap code, which relies on very simple, old javascript
to at least display a helpful error message if we can't use OliveTin.
*/
window.showBigError = function (type, friendlyType, message, isFatal) {
console.error('Error ' + type + ': ', message)
return;
bigErrorDialog.innerHTML = '<h1>Error ' + friendlyType + '</h1><p>' + message + "</p><p><a href = 'http://docs.olivetin.app/troubleshooting/err-" + type + ".html' target = 'blank'/>" + type + " error in OliveTin Documentation</a></p>"
if (isFatal) {
bigErrorDialog.innerHTML += '<p>You will need to refresh your browser to clear this message.</p>'
} else {
bigErrorDialog.innerHTML += '<p>This error message will go away automatically if the problem is solved.</p>'
}
bigErrorDialog.showModal()
console.error('Error ' + type + ': ', message)
}
window.clearBigErrors = function () {
bigErrorDialog.close()
}
</script>
<script type = "text/javascript" nomodule>
showBigError("js-modules-not-supported", "Sorry, your browser does not support JavaScript modules.", null)
</script>
<script type = "module" src = "main.js"></script>
</body>
</html>

24
frontend/js/Mutex.js Normal file
View File

@@ -0,0 +1,24 @@
export class Mutex {
constructor () {
this._locked = false
this._waiting = []
}
lock () {
const unlock = () => {
const next = this._waiting.shift()
if (next) {
next(unlock)
} else {
this._locked = false
}
}
if (this._locked) {
return new Promise(resolve => this._waiting.push(resolve)).then(() => unlock)
} else {
this._locked = true
return Promise.resolve(unlock)
}
}
}

View File

@@ -0,0 +1,77 @@
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { Mutex } from './Mutex.js'
/**
* xterm.js based terminal output for the execution dialog.
*
* the xterm.js methods for write(), reset() and clear() appear to be async,
* but they do not return a Promise and instead use a callback. When calling
* these methods in quick succession, the output can get garbled due to race
* conditions.
*
* To avoid this, this class uses Mutex around those methods to ensure that
* only one write OR reset is executed at a time, is completed, and the calls
* occour in sequential order.
*/
export class OutputTerminal {
constructor (executionTrackingId) {
this.executionTrackingId = executionTrackingId
this.writeMutex = new Mutex()
this.terminal = new Terminal({
convertEol: true
})
const fitAddon = new FitAddon()
this.terminal.loadAddon(fitAddon)
this.terminal.fit = fitAddon
}
async write (out, then) {
const unlock = await this.writeMutex.lock()
try {
await new Promise(resolve => {
this.terminal.write(out, () => {
resolve()
})
})
} finally {
unlock()
if (then != null && then !== undefined) {
then()
}
}
}
async reset () {
const unlock = await this.writeMutex.lock()
try {
await new Promise(resolve => {
this.terminal.clear()
this.terminal.reset()
resolve()
})
} finally {
unlock()
}
}
fit () {
this.terminal.fit.fit()
}
open (el) {
this.terminal.open(el)
}
close () {
this.terminal.dispose()
}
resize (cols, rows) {
this.terminal.resize(cols, rows)
}
}

61
frontend/js/websocket.js Normal file
View File

@@ -0,0 +1,61 @@
import { buttonResults } from '../resources/vue/stores/buttonResults.js'
export function initWebsocket () {
window.addEventListener('EventOutputChunk', onOutputChunk)
reconnectWebsocket()
}
window.websocketAvailable = false
async function reconnectWebsocket () {
if (window.websocketAvailable) {
return
}
try {
window.websocketAvailable = true
for await (const e of window.client.eventStream()) {
handleEvent(e)
}
} catch (err) {
console.error('Websocket connection failed: ', err)
}
window.websocketAvailable = false
console.log('Reconnecting websocket...')
}
function handleEvent (msg) {
const typeName = msg.event.value.$typeName.replace('olivetin.api.v1.', '')
const j = new Event(typeName)
j.payload = msg.event.value
switch (typeName) {
case 'EventOutputChunk':
case 'EventConfigChanged':
case 'EventEntityChanged':
window.dispatchEvent(j)
break
case 'EventExecutionFinished':
case 'EventExecutionStarted':
buttonResults[msg.event.value.logEntry.executionTrackingId] = msg.event.value.logEntry
window.dispatchEvent(j)
break
default:
console.warn('Unhandled websocket message type from server: ', typeName)
window.showBigError('ws-unhandled-message', 'handling websocket message', 'Unhandled websocket message type from server: ' + typeName, true)
}
}
function onOutputChunk (evt) {
const chunk = evt.payload
if (window.terminal) {
if (chunk.executionTrackingId === window.terminal.executionTrackingId) {
window.terminal.write(chunk.output)
}
}
}

123
frontend/main.js Normal file
View File

@@ -0,0 +1,123 @@
'use strict'
import 'femtocrank/style.css'
import 'femtocrank/dark.css'
import './style.css'
import 'iconify-icon'
import { createClient } from '@connectrpc/connect'
import { createConnectTransport } from '@connectrpc/connect-web'
import { OliveTinApiService } from './resources/scripts/gen/olivetin/api/v1/olivetin_pb'
import { createApp, h } from 'vue'
import { createI18n } from 'vue-i18n'
import router from './resources/vue/router.js'
import App from './resources/vue/App.vue'
import { initWebsocket } from './js/websocket.js'
import combinedTranslations from '../lang/combined_output.json'
function getSelectedLanguage () {
const storedLanguage = localStorage.getItem('olivetin-language')
if (storedLanguage && storedLanguage !== 'auto') {
return storedLanguage
}
if (storedLanguage === 'auto') {
localStorage.removeItem('olivetin-language')
}
if (navigator.languages && navigator.languages.length > 0) {
const available = Object.keys(combinedTranslations.messages || {})
for (const candidate of navigator.languages) {
const lowerCandidate = candidate.toLowerCase()
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
if (exact) {
return exact
}
const prefix = available.find(locale => locale.toLowerCase().startsWith(lowerCandidate.split('-')[0] + '-'))
if (prefix) {
return prefix
}
}
}
return 'en'
}
async function initClient () {
const transport = createConnectTransport({
baseUrl: window.location.protocol + '//' + window.location.host + '/api/'
})
window.client = createClient(OliveTinApiService, transport)
window.initResponse = await window.client.init({})
const i18nSettings = createI18n({
legacy: false,
locale: getSelectedLanguage(),
fallbackLocale: 'en',
messages: combinedTranslations.messages,
postTranslation: (translated) => {
const params = new URLSearchParams(window.location.search)
if (params.has('debug-translations')) {
return '____'
} else {
return translated
}
}
})
return i18nSettings
}
function setupVue (i18nSettings) {
const app = createApp(App)
app.use(router)
app.use(i18nSettings)
window.i18n = i18nSettings.global
app.mount('#app')
}
function setupErrorDisplay (errorMessage) {
const ErrorApp = {
render() {
return h('section', { class: 'bad', style: 'padding: 2em; text-align: center; margin: 2em auto;' }, [
h('h2', 'OliveTin Init Failed'),
h('p', errorMessage),
h('p', 'Please check your browser console for more details.')
])
}
}
const app = createApp(ErrorApp)
app.mount('#app')
}
async function main () {
try {
const i18nSettings = await initClient()
initWebsocket()
setupVue(i18nSettings)
} catch (err) {
const errorMessage = err.message || 'Failed to initialize. Please check your configuration and try again.'
console.error('Init failed:', err)
setupErrorDisplay(errorMessage)
}
}
main()

5883
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
frontend/package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "olivetin-webui",
"version": "1.0.0",
"description": "The WebUI for OliveTin",
"repository": "https://github.com/OliveTin/OliveTin",
"source": "index.html",
"devDependencies": {
"process": "^0.11.10",
"stylelint": "^16.26.1",
"stylelint-config-standard": "^39.0.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"parcelIgnore": [
"theme.css",
"OliveTinLogo.png",
"OliveTinLogo-57px.png",
"OliveTinLogo-120px.png",
"OliveTinLogo-180px.png"
],
"license": "AGPL-3.0-only",
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
"@hugeicons/core-free-icons": "^2.0.0",
"@hugeicons/vue": "^1.0.3",
"@vitejs/plugin-vue": "^6.0.2",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"iconify-icon": "^3.0.2",
"picocrank": "^1.9.0",
"standard": "^17.1.2",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.2.4",
"vue-i18n": "^11.2.2",
"vue-router": "^4.6.3"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,312 @@
<template>
<div :id="`actionButton-${actionId}`" role="none" class="action-button">
<button :id="`actionButtonInner-${actionId}`" :title="title" :disabled="!canExec || isDisabled"
:class="buttonClasses" @click="handleClick">
<div 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">
<HugeiconsIcon :icon="TypeCursorIcon" />
</div>
<div v-if="navigateOnStart == ''" class="navigate-on-start" title="Run in the background">
<HugeiconsIcon :icon="WorkoutRunIcon" />
</div>
</div>
<span class="icon" v-html="unicodeIcon"></span>
<span class="title" aria-live="polite">{{ displayTitle }}
</span>
</button>
</div>
</template>
<script setup>
import ArgumentForm from './views/ArgumentForm.vue'
import { buttonResults } from './stores/buttonResults'
import { useRouter } from 'vue-router'
import { HugeiconsIcon } from '@hugeicons/vue'
import { WorkoutRunIcon, TypeCursorIcon, ComputerTerminal01Icon } from '@hugeicons/core-free-icons'
import { ref, watch, onMounted, inject } from 'vue'
const router = useRouter()
const navigateOnStart = ref('')
const props = defineProps({
actionData: {
type: Object,
required: true
}
})
const actionId = ref('')
const title = ref('')
const canExec = ref(true)
const popupOnStart = ref('')
// Display properties
const unicodeIcon = ref('&#x1f4a9;')
const displayTitle = ref('')
// State
const isDisabled = ref(false)
const showArgumentForm = ref(false)
// Animation classes
const buttonClasses = ref([])
// Timestamps
const updateIterationTimestamp = ref(0)
function getUnicodeIcon(icon) {
if (icon === '') {
console.log('icon not found ', icon)
return '&#x1f4a9;'
} else {
return unescape(icon)
}
}
function constructFromJson(json) {
updateIterationTimestamp.value = 0
updateFromJson(json)
actionId.value = json.bindingId
title.value = json.title
canExec.value = json.canExec
popupOnStart.value = json.popupOnStart
if (popupOnStart.value.includes('execution-dialog')) {
navigateOnStart.value = 'pop'
} else if (props.actionData.arguments.length > 0) {
navigateOnStart.value = 'arg'
}
isDisabled.value = !json.canExec
displayTitle.value = title.value
unicodeIcon.value = getUnicodeIcon(json.icon)
}
function updateFromJson(json) {
// Fields that should not be updated
// title - as the callback URL relies on it
unicodeIcon.value = getUnicodeIcon(json.icon)
}
async function handleClick() {
if (props.actionData.arguments && props.actionData.arguments.length > 0) {
router.push(`/actionBinding/${props.actionData.bindingId}/argumentForm`)
} else {
await startAction()
}
}
function getUniqueId() {
if (window.isSecureContext) {
return window.crypto.randomUUID()
} else {
return Date.now().toString()
}
}
async function startAction(actionArgs) {
buttonClasses.value = [] // Removes old animation classes
if (actionArgs === undefined) {
actionArgs = []
}
// UUIDs are create client side, so that we can setup a "execution-button"
// to track the execution before we send the request to the server.
const startActionArgs = {
bindingId: props.actionData.bindingId,
arguments: actionArgs,
uniqueTrackingId: getUniqueId()
}
console.log('Watching buttonResults for', startActionArgs.uniqueTrackingId)
watch(
() => buttonResults[startActionArgs.uniqueTrackingId],
(newResult, oldResult) => {
onLogEntryChanged(newResult)
}
)
try {
await window.client.startAction(startActionArgs)
} catch (err) {
console.error('Failed to start action:', err)
}
}
function onLogEntryChanged(logEntry) {
if (logEntry.executionFinished) {
onExecutionFinished(logEntry)
} else {
onExecutionStarted(logEntry)
}
}
function onExecutionStarted(logEntry) {
if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
router.push(`/logs/${logEntry.executionTrackingId}`)
}
isDisabled.value = true
}
function onExecutionFinished(logEntry) {
if (logEntry.timedOut) {
renderExecutionResult('action-timeout', 'Timed out')
} else if (logEntry.blocked) {
renderExecutionResult('action-blocked', 'Blocked!')
} else if (logEntry.exitCode !== 0) {
renderExecutionResult('action-nonzero-exit', 'Exit code ' + logEntry.exitCode)
} else {
const ellapsed = Math.ceil(new Date(logEntry.datetimeFinished) - new Date(logEntry.datetimeStarted)) / 1000
renderExecutionResult('action-success', 'Success!')
}
}
function renderExecutionResult(resultCssClass, temporaryStatusMessage) {
updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
onExecStatusChanged()
}
function updateDom(resultCssClass, newTitle) {
if (resultCssClass == null) {
buttonClasses.value = []
} else {
buttonClasses.value = [resultCssClass]
}
displayTitle.value = newTitle
}
function onExecStatusChanged() {
isDisabled.value = false
setTimeout(() => {
updateDom(null, title.value)
}, 2000)
}
onMounted(() => {
constructFromJson(props.actionData)
})
watch(
() => props.actionData,
(newData) => {
updateFromJson(newData)
},
{ deep: true }
)
</script>
<style scoped>
.action-button {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.action-button button {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 0 .6em #aaa;
font-size: .85em;
border-radius: .7em;
}
.action-button button:hover:not(:disabled) {
background: #f5f5f5;
border-color: #999;
}
.action-button button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.action-button button .icon {
font-size: 3em;
flex-grow: 1;
align-content: center;
}
.action-button button .title {
font-weight: 500;
padding: 0.2em;
}
/* Animation classes */
.action-button button.action-timeout {
background: #fff3cd;
border-color: #ffeaa7;
color: #856404;
}
.action-button button.action-blocked {
background: #f8d7da !important;
border-color: #f5c6cb;
color: #721c24;
}
.action-button button.action-nonzero-exit {
background: #f8d7da !important;
border-color: #f5c6cb;
color: #721c24;
}
.action-button button.action-success {
background: #d4edda !important;
border-color: #c3e6cb;
color: #155724;
}
.action-button-footer {
margin-top: 0.5em;
}
.navigate-on-start-container {
position: relative;
margin-left: auto;
height: 0;
right: 0;
top: 0;
}
@media (prefers-color-scheme: dark) {
.action-button button {
background: #111;
border-color: #000;
box-shadow: 0 0 6px #000;
color: #fff;
}
.action-button button:hover:not(:disabled) {
background: #222;
border-color: #000;
box-shadow: 0 0 6px #444;
color: #fff;
}
}
</style>

View File

@@ -0,0 +1,350 @@
<template>
<Header :title="pageTitle" :logoUrl="logoUrl" @toggleSidebar="toggleSidebar" :sidebarEnabled="showNavigation">
<template #toolbar>
<div id="banner" v-if="bannerMessage" :style="bannerCss">
<p>{{ bannerMessage }}</p>
</div>
</template>
<template #user-info>
<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">
<span id="username-text">{{ username }}</span>
</router-link>
<HugeiconsIcon :icon="UserCircle02Icon" width = "1.5em" height = "1.5em" v-if="isLoggedIn" />
</div>
</template>
</Header>
<div id="layout">
<Sidebar ref="sidebar" id = "mainnav" v-if="showNavigation" />
<div id="content" initial-martial-complete="{{ hasLoaded }}">
<main title="Main content">
<router-view :key="$route.fullPath" />
</main>
<footer title="footer" v-if="showFooter">
<p>
<img title="application icon" :src="logoUrl" alt="OliveTin logo" style="height: 1em;" class="logo" />
OliveTin {{ currentVersion }}
</p>
<p>
<span>
<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>
</span>
<span>
<a href="#" @click.prevent="openLanguageDialog">{{ currentLanguageName }}</a>
</span>
<span>{{ t('connected') }}</span>
</p>
<p>
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a>
</p>
</footer>
</div>
</div>
<dialog ref="languageDialog" class="language-dialog" @click="handleDialogClick">
<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">
{{ code === 'auto' ? name : `${name} (${code})` }}
</option>
</select>
<p class="browser-languages">
{{ t('language-dialog.browser-languages') }}:
<span v-if="browserLanguages.length > 0">{{ browserLanguages.join(', ') }}</span>
<span v-else>{{ t('language-dialog.not-available') }}</span>
</p>
<div class="dialog-buttons">
<button @click="closeLanguageDialog">{{ t('language-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 Header from 'picocrank/vue/components/Header.vue';
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();
const router = useRouter();
const sidebar = ref(null);
const username = ref('notset');
const isLoggedIn = ref(false);
const serverConnection = ref(true);
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)
const showDiagnostics = ref(true)
const showLoginLink = ref(true)
const languageDialog = ref(null)
const browserLanguages = ref([])
const initialLanguagePreference = typeof window !== 'undefined' ? localStorage.getItem('olivetin-language') : null
const languagePreference = ref(initialLanguagePreference || 'auto')
const selectedLanguage = ref(languagePreference.value)
// Available languages with display names
const availableLanguages = {
'auto': 'Browser Language',
'en': 'English',
'de-DE': 'Deutsch',
'es-ES': 'Español',
'it-IT': 'Italiano',
'zh-Hans-CN': '简体中文'
}
// Computed property to get current language display name
const currentLanguageName = computed(() => {
if (languagePreference.value === 'auto') {
return availableLanguages['auto']
}
return availableLanguages[languagePreference.value] || languagePreference.value
})
function normalizeBrowserLanguage() {
const available = Object.keys(combinedTranslations.messages || {})
if (navigator.languages && navigator.languages.length > 0) {
for (const candidate of navigator.languages) {
const lowerCandidate = candidate.toLowerCase()
// Try exact match (case-insensitive)
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
if (exact) {
return exact
}
// Try prefix match (e.g., "zh-CN" -> "zh-Hans-CN")
const prefix = available.find(locale => locale.toLowerCase().startsWith(lowerCandidate.split('-')[0] + '-'))
if (prefix) {
return prefix
}
}
}
return 'en'
}
function toggleSidebar() {
if (sidebar.value && showNavigation.value) {
sidebar.value.toggle()
}
}
function updateHeaderFromInit() {
if (!window.initResponse) {
return
}
username.value = window.initResponse.authenticatedUser
isLoggedIn.value = window.initResponse.authenticatedUser !== '' && window.initResponse.authenticatedUser !== 'guest'
currentVersion.value = window.initResponse.currentVersion
pageTitle.value = window.initResponse.pageTitle || 'OliveTin'
bannerMessage.value = window.initResponse.bannerMessage || ''
bannerCss.value = window.initResponse.bannerCss || ''
showFooter.value = window.initResponse.showFooter
showNavigation.value = window.initResponse.showNavigation
showLogs.value = window.initResponse.showLogList
showDiagnostics.value = window.initResponse.showDiagnostics
if (!window.initResponse.authLocalLogin && window.initResponse.oAuth2Providers.length === 0) {
showLoginLink.value = false
}
renderSidebar()
if (window.initResponse.loginRequired) {
router.push('/login')
return
}
}
function renderSidebar() {
if (!sidebar.value) {
return
}
const rootDashboards = window.initResponse?.rootDashboards || []
if (typeof sidebar.value.clear === 'function') {
sidebar.value.clear()
}
for (const rootDashboard of rootDashboards) {
sidebar.value.addNavigationLink({
id: rootDashboard,
name: rootDashboard,
title: rootDashboard,
path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
icon: DashboardSquare01Icon,
})
}
sidebar.value.addSeparator()
sidebar.value.addRouterLink('Entities', t('nav.entities'))
if (showLogs.value) {
sidebar.value.addRouterLink('Logs', t('nav.logs'))
}
if (showDiagnostics.value) {
sidebar.value.addRouterLink('Diagnostics', t('nav.diagnostics'))
}
}
function openLanguageDialog() {
selectedLanguage.value = languagePreference.value
if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) {
browserLanguages.value = navigator.languages
} else {
browserLanguages.value = []
}
if (languageDialog.value) {
languageDialog.value.showModal()
}
}
function closeLanguageDialog() {
if (languageDialog.value) {
languageDialog.value.close()
}
}
function changeLanguage() {
if (!window.i18n || !selectedLanguage.value) {
return
}
if (selectedLanguage.value === 'auto') {
localStorage.removeItem('olivetin-language')
languagePreference.value = 'auto'
window.i18n.locale.value = normalizeBrowserLanguage()
} else {
window.i18n.locale.value = selectedLanguage.value
localStorage.setItem('olivetin-language', selectedLanguage.value)
languagePreference.value = selectedLanguage.value
}
// Update sidebar with new translations
if (sidebar.value) {
renderSidebar()
}
closeLanguageDialog()
}
function handleDialogClick(event) {
// Close dialog when clicking on the backdrop
if (event.target === languageDialog.value) {
closeLanguageDialog()
}
}
window.updateHeaderFromInit = updateHeaderFromInit
onMounted(() => {
serverConnection.value = true;
updateHeaderFromInit()
// Initialize selected language from stored preference
selectedLanguage.value = languagePreference.value
if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) {
browserLanguages.value = navigator.languages
}
})
</script>
<style scoped>
.user-info span {
margin-left: 1em;
}
.user-link {
text-decoration: none;
color: inherit;
}
.user-link:hover {
text-decoration: underline;
}
.language-dialog {
border: 1px solid var(--border-color, #ccc);
border-radius: 0.5rem;
padding: 0;
max-width: 400px;
width: 90%;
}
.language-dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
.dialog-content {
padding: 1.5rem;
}
.dialog-content h2 {
margin-top: 0;
margin-bottom: 1rem;
}
.language-select {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
font-size: 1rem;
border: 1px solid var(--border-color, #ccc);
border-radius: 0.25rem;
}
.dialog-buttons {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.dialog-buttons button {
padding: 0.5rem 1rem;
cursor: pointer;
}
.browser-languages {
font-size: 0.875rem;
color: var(--fg2, #555);
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,298 @@
<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;" />
<p>Loading dashboard...</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>
<p>{{ initError }}</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" />
<span>Back</span>
</button>
</div>
<h2>{{ dashboard.title }}</h2>
<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" />
<span>Back</span>
</button>
</div>
<div class = "dashboard-row" v-for="component in dashboard.contents" :key="component.title">
<h2 v-if = "dashboard.title != 'Default'">
<router-link
v-if="component.entityType && component.entityKey"
:to="{
name: 'EntityDetails',
params: {
entityType: component.entityType,
entityKey: component.entityKey
}
}"
class="entity-link">
{{ component.title }}
</router-link>
<span v-else>{{ component.title }}</span>
</h2>
<fieldset>
<template v-for="subcomponent in component.contents">
<DashboardComponent :component="subcomponent" />
</template>
</fieldset>
</div>
</section>
</template>
</template>
<script setup>
import DashboardComponent from './components/DashboardComponent.vue'
import { onMounted, onUnmounted, ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { HugeiconsIcon } from '@hugeicons/vue'
import { Loading03Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
const props = defineProps({
title: {
type: String,
required: false
},
entityType: {
type: String,
required: false
},
entityKey: {
type: String,
required: false
}
})
const router = useRouter()
const dashboard = ref(null)
const loadingTime = ref(0)
const initError = ref(null)
let loadingTimer = null
let checkInitInterval = null
const isDirectory = computed(() => {
if (!dashboard.value || !window.initResponse) {
return false
}
const rootDashboards = window.initResponse.rootDashboards || []
return !rootDashboards.includes(dashboard.value.title) && dashboard.value.title !== 'Actions'
})
function goBack() {
if (window.history.length > 1) {
router.back()
} else {
const rootDashboards = window.initResponse?.rootDashboards || []
if (rootDashboards.length > 0) {
router.push({ name: 'Dashboard', params: { title: rootDashboards[0] } })
} else {
router.push({ name: 'Actions' })
}
}
}
async function getDashboard() {
let title = props.title
// If no specific title was provided or it's the placeholder 'default',
// prefer the first configured root dashboard (e.g., "Test").
if ((!title || title === 'default') && window.initResponse.rootDashboards && window.initResponse.rootDashboards.length > 0) {
title = window.initResponse.rootDashboards[0]
}
try {
const request = {
title: title,
}
if (props.entityType && props.entityKey) {
request.entityType = props.entityType
request.entityKey = props.entityKey
}
const ret = await window.client.getDashboard(request)
if (!ret || !ret.dashboard) {
throw new Error('No dashboard found')
}
dashboard.value = ret.dashboard
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = ret.dashboard.title + ' - ' + pageTitle
// Clear any previous init error since we successfully loaded
initError.value = null
// Stop the loading timer once dashboard is loaded
if (loadingTimer) {
clearInterval(loadingTimer)
loadingTimer = null
}
// Set attribute to indicate dashboard is loaded successfully
document.body.setAttribute('loaded-dashboard', title || 'default')
} catch (e) {
// On error, provide a safe fallback state
console.error('Failed to load dashboard', e)
dashboard.value = { title: title || 'Default', contents: [] }
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = 'Error - ' + pageTitle
// Stop the loading timer on error
if (loadingTimer) {
clearInterval(loadingTimer)
loadingTimer = null
}
// Set attribute even on error so tests can proceed
document.body.setAttribute('loaded-dashboard', title || 'error')
}
}
function waitForInitAndLoadDashboard() {
// Start the loading timer
loadingTime.value = 0
loadingTimer = setInterval(() => {
loadingTime.value++
}, 1000)
// Check if init has completed successfully
if (window.initResponse) {
getDashboard()
} else if (window.initError) {
// Init failed, show error immediately
initError.value = window.initErrorMessage || 'Initialization failed. Please check your configuration and try again.'
// Stop the loading timer since we're showing an error
if (loadingTimer) {
clearInterval(loadingTimer)
loadingTimer = null
}
} else {
// Init hasn't completed yet, poll for completion
checkInitInterval = setInterval(() => {
if (window.initResponse) {
clearInterval(checkInitInterval)
checkInitInterval = null
getDashboard()
} else if (window.initError) {
clearInterval(checkInitInterval)
checkInitInterval = null
initError.value = window.initErrorMessage || 'Initialization failed. Please check your configuration and try again.'
// Stop the loading timer since we're showing an error
if (loadingTimer) {
clearInterval(loadingTimer)
loadingTimer = null
}
}
}, 100) // Check every 100ms
}
}
onMounted(() => {
waitForInitAndLoadDashboard()
})
onUnmounted(() => {
// Clean up the timers when component is unmounted
if (loadingTimer) {
clearInterval(loadingTimer)
loadingTimer = null
}
if (checkInitInterval) {
clearInterval(checkInitInterval)
checkInitInterval = null
}
})
</script>
<style scoped>
h2 {
font-weight: bold;
text-align: center;
padding: 1em;
padding-top: 1.5em;
grid-column: 1 / -1;
}
h2 .entity-link {
color: inherit;
text-decoration: none;
transition: opacity 0.2s;
}
h2 .entity-link:hover {
opacity: 0.7;
text-decoration: underline;
}
fieldset {
display: grid;
grid-template-columns: repeat(auto-fit, 180px);
grid-auto-rows: 1fr;
justify-content: center;
place-items: stretch;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.back-button-container {
display: flex;
justify-content: flex-start;
padding: 1em;
padding-bottom: 0;
}
.back-button {
display: flex;
align-items: center;
gap: 0.5em;
padding: 0.5em 1em;
background-color: var(--bg, #fff);
border: 1px solid var(--border-color, #ccc);
border-radius: 0.5em;
cursor: pointer;
font-size: 0.9em;
box-shadow: 0 0 .3em rgba(0, 0, 0, 0.1);
transition: background-color 0.2s, box-shadow 0.2s;
}
.back-button:hover {
background-color: var(--bg-hover, #f5f5f5);
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.15);
}
@media (prefers-color-scheme: dark) {
.back-button {
background-color: var(--bg, #111);
border-color: var(--border-color, #333);
}
.back-button:hover {
background-color: var(--bg-hover, #222);
}
}
</style>

View File

@@ -0,0 +1,141 @@
<template>
<div
:id="`execution-${executionTrackingId}`"
class="execution-button"
>
<button
:title="`${ellapsed}s`"
@click="show"
>
{{ buttonText }}
</button>
</div>
</template>
<script>
//import { ExecutionFeedbackButton } from '../js/ExecutionFeedbackButton.js'
export default {
name: 'ExecutionButton',
// mixins: [ExecutionFeedbackButton],
props: {
executionTrackingId: {
type: String,
required: true
}
},
data() {
return {
ellapsed: 0,
isWaiting: true
}
},
computed: {
buttonText() {
if (this.isWaiting) {
return 'Executing...'
} else {
return `${this.ellapsed}s`
}
}
},
mounted() {
this.constructFromJson(this.executionTrackingId)
},
methods: {
constructFromJson(json) {
this.executionTrackingId = json
this.ellapsed = 0
this.isWaiting = true
},
show() {
this.$emit('show')
if (window.executionDialog) {
window.executionDialog.reset()
window.executionDialog.show()
window.executionDialog.fetchExecutionResult(this.executionTrackingId)
}
},
onExecStatusChanged() {
this.isWaiting = false
this.domTitle = this.ellapsed + 's'
},
// Override from ExecutionFeedbackButton
onExecutionFinished(logEntry) {
if (logEntry.timedOut) {
this.renderExecutionResult('action-timeout', 'Timed out')
} else if (logEntry.blocked) {
this.renderExecutionResult('action-blocked', 'Blocked!')
} else if (logEntry.exitCode !== 0) {
this.renderExecutionResult('action-nonzero-exit', 'Exit code ' + logEntry.exitCode)
} else {
this.ellapsed = Math.ceil(new Date(logEntry.datetimeFinished) - new Date(logEntry.datetimeStarted)) / 1000
this.renderExecutionResult('action-success', 'Success!')
}
},
renderExecutionResult(resultCssClass, temporaryStatusMessage) {
this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
this.onExecStatusChanged()
},
updateDom(resultCssClass, title) {
// For execution button, we don't need to update classes as much
// since it's a simpler component
if (resultCssClass) {
this.$el.classList.add(resultCssClass)
}
}
}
}
</script>
<style scoped>
.execution-button {
display: inline-block;
}
.execution-button button {
padding: 0.25em 0.5em;
border: 1px solid #ccc;
border-radius: 3px;
background: #fff;
cursor: pointer;
font-size: 0.9em;
transition: all 0.2s ease;
}
.execution-button button:hover {
background: #f5f5f5;
border-color: #999;
}
/* Animation classes */
.execution-button button.action-timeout {
background: #fff3cd;
border-color: #ffeaa7;
color: #856404;
}
.execution-button button.action-blocked {
background: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
.execution-button button.action-nonzero-exit {
background: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
.execution-button button.action-success {
background: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div :class = "statusClass + ' annotation'">
<span>{{ statusText }}</span><span>{{ exitCodeText }}</span>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
logEntry: {
type: Object,
required: true
}
})
const statusText = computed(() => {
const logEntry = props.logEntry
if (!logEntry) return 'unknown'
if (logEntry.executionFinished) {
if (logEntry.blocked) {
return 'Blocked'
} else if (logEntry.timedOut) {
return 'Timed out'
} else {
return 'Completed'
}
} else {
return 'Still running...'
}
})
const exitCodeText = computed(() => {
const logEntry = props.logEntry
if (!logEntry) return ''
if (logEntry.exitCode === 0) {
return ''
}
if (logEntry.executionFinished) {
if (logEntry.blocked || logEntry.timedOut) {
return ''
}
return ' (Exit code: ' + logEntry.exitCode + ')'
}
return ''
})
const statusClass = computed(() => {
const logEntry = props.logEntry
if (!logEntry) return ''
if (logEntry.executionFinished) {
if (logEntry.blocked) {
return 'status-blocked'
} else if (logEntry.timedOut) {
return 'status-timeout'
} else if (logEntry.exitCode === 0) {
return 'status-success'
} else {
return 'status-nonzero-exit'
}
}
return ''
})
</script>
<style scoped>
.status-success {
color: var(--karma-good-fg);
}
.status-nonzero-exit {
color: var(--karma-bad-fg);
}
.status-timeout {
color: var(--karma-warning-fg);
}
.status-blocked {
color: #ca79ff;
}
</style>

View File

@@ -0,0 +1,58 @@
<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">
&raquo;
</span>
</template>
</div>
</template>
<style scoped>
span {
color: #bbb;
}
a {
text-decoration: none;
padding: 0.4em;
border-radius: 0.2em;
}
a:hover {
text-decoration: underline;
background-color: #000;
}
</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.meta && record.meta.breadcrumb) {
record.meta.breadcrumb.forEach((item) => {
links.value.push({
name: item.name,
href: item.href || record.path || '/'
});
});
} else if (record.name) {
links.value.push({
name: record.name,
href: record.path || '/'
});
}
});
}, { immediate: true });
</script>

View File

@@ -0,0 +1,35 @@
<template>
<ActionButton v-if="component.type == 'link'" :actionData="component.action" :key="component.title" />
<DashboardComponentDirectory v-else-if="component.type == 'directory'" :component="component" />
<DashboardComponentDisplay v-else-if="component.type == 'display'" :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">
<DashboardComponent :component="subcomponent" />
</template>
</template>
<div v-else>
OTHER: {{ component.type }}
{{ component }}
</div>
</template>
<script setup>
import ActionButton from '../ActionButton.vue'
import DashboardComponentMostRecentExecution from './DashboardComponentMostRecentExecution.vue'
import DashboardComponentDirectory from './DashboardComponentDirectory.vue'
import DashboardComponentDisplay from './DashboardComponentDisplay.vue'
const props = defineProps({
component: {
type: Object,
required: true
}
})
</script>

View File

@@ -0,0 +1,60 @@
<template>
<button @click="navigateToDirectory">
{{ component.title }}
</button>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const props = defineProps({
component: {
type: Object,
required: true
}
})
function navigateToDirectory() {
const params = { title: props.component.title }
if (props.component.entityType && props.component.entityKey) {
params.entityType = props.component.entityType
params.entityKey = props.component.entityKey
}
router.push({ name: 'Dashboard', params })
}
</script>
<style scoped>
.folder-container {
display: grid;
}
button {
box-shadow: 0 0 .6em #aaa;
background-color: #fff;
border-radius: .7em;
}
button:hover {
background-color: #f5f5f5;
border-color: #999;
}
@media (prefers-color-scheme: dark) {
button {
box-shadow: 0 0 .6em #000;
background-color: #111;
border-color: #000;
}
button:hover {
background-color: #222;
border-color: #000;
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="display">
<div v-html="component.title" />
</div>
</template>
<script setup>
const props = defineProps({
component: {
type: Object,
required: true
}
})
</script>
<style scoped>
.display {
padding: 1em;
border-radius: .7em;
box-shadow: 0 0 .6em #aaa;
text-align: center;
font-size: small;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: center;
}
@media (prefers-color-scheme: dark) {
.display {
border-color: #000;
box-shadow: 0 0 .6em #000;
}
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<div class="mre-container">
<router-link
v-if="executionTrackingId"
:to="`/logs/${executionTrackingId}`"
class="mre-link"
>
<pre class="mre-output">{{ output }}</pre>
</router-link>
<pre v-else class="mre-output fg-important">{{ output }}</pre>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
component: {
type: Object,
required: true
}
})
const output = ref('Waiting...')
const executionTrackingId = ref(null)
let eventListener = null
async function fetchMostRecentExecution() {
if (!props.component.title) {
output.value = 'Error: No action ID specified'
executionTrackingId.value = null
return
}
if (!window.client) {
output.value = 'Error: Client not initialized'
executionTrackingId.value = null
return
}
try {
const executionStatusArgs = {
actionId: props.component.title
}
const result = await window.client.executionStatus(executionStatusArgs)
if (result.logEntry) {
if (result.logEntry.output !== undefined) {
output.value = result.logEntry.output
} else {
output.value = 'No output available'
}
if (result.logEntry.executionTrackingId) {
executionTrackingId.value = result.logEntry.executionTrackingId
}
} else {
output.value = 'No output available'
executionTrackingId.value = null
}
} catch (err) {
if (err.code === 'NotFound' || err.status === 404) {
output.value = 'No execution found'
executionTrackingId.value = null
} else {
output.value = 'Error: ' + (err.message || 'Failed to fetch execution')
console.error('Failed to fetch most recent execution:', err)
executionTrackingId.value = null
}
}
}
function handleExecutionFinished(event) {
// The dashboard component "title" field is used for lots of things
// and in this context for MreOutput it's just to refer to an actionId.
//
// So this is not a typo.
const logEntry = event.payload.logEntry
if (logEntry && logEntry.actionId === props.component.title) {
if (logEntry.output !== undefined) {
output.value = logEntry.output
}
if (logEntry.executionTrackingId) {
executionTrackingId.value = logEntry.executionTrackingId
}
}
}
onMounted(() => {
fetchMostRecentExecution()
eventListener = (event) => handleExecutionFinished(event)
window.addEventListener('EventExecutionFinished', eventListener)
})
onBeforeUnmount(() => {
if (eventListener) {
window.removeEventListener('EventExecutionFinished', eventListener)
}
})
</script>
<style scoped>
.mre-container {
display: grid;
grid-column: span 2;
}
.mre-link {
text-decoration: none;
color: inherit;
display: grid;
cursor: pointer;
grid-column: span 2;
}
.mre-link:hover .mre-output {
border-color: #999;
}
.mre-output {
box-shadow: 0 0 .6em #aaa;
border: 1px dashed #ccc;
border-radius: .7em;
padding: 1em;
margin: 0;
min-height: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: monospace;
font-size: 0.9em;
overflow-x: auto;
overflow-y: auto;
transition: border-color 0.2s ease;
max-height: 20em;
}
@media (prefers-color-scheme: dark) {
.mre-output {
border: 1px dashed #444;
box-shadow: 0 0 .6em #444;
}
.mre-link:hover .mre-output {
border-color: #666;
}
}
</style>

View File

@@ -0,0 +1,149 @@
import { createRouter, createWebHistory } from 'vue-router'
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'
const routes = [
{
path: '/',
name: 'Actions',
component: () => import('./Dashboard.vue'),
meta: { title: 'Actions', icon: DashboardSquare01Icon }
},
{
path: '/dashboards/:title/:entityType?/:entityKey?',
name: 'Dashboard',
component: () => import('./Dashboard.vue'),
props: true,
meta: { title: 'Dashboard' }
},
{
path: '/actionBinding/:bindingId/argumentForm',
name: 'ActionBinding',
component: () => import('./views/ArgumentForm.vue'),
props: true,
meta: { title: 'Action Binding' }
},
{
path: '/logs',
name: 'Logs',
component: () => import('./views/LogsListView.vue'),
meta: {
title: 'Logs',
icon: LeftToRightListDashIcon
}
},
{
path: '/entities',
name: 'Entities',
component: () => import('./views/EntitiesView.vue'),
meta: {
title: 'Entities',
icon: CellsIcon
}
},
{
path: '/entity-details/:entityType/:entityKey',
name: 'EntityDetails',
component: () => import('./views/EntityDetailsView.vue'),
props: true,
meta: {
title: 'OliveTin - Entity Details',
breadcrumb: [
{ name: "Entities", href: "/entities" },
{ name: "Entity Details" }
]
}
},
{
path: '/logs/:executionTrackingId',
name: 'Execution',
component: () => import('./views/ExecutionView.vue'),
props: true,
meta: {
title: 'Execution',
breadcrumb: [
{ name: "Logs", href: "/logs" },
{ name: "Execution" },
]
}
},
{
path: '/action/:actionId',
name: 'ActionDetails',
component: () => import('./views/ActionDetailsView.vue'),
props: true,
meta: {
title: 'Action Details',
breadcrumb: [
{ name: "Actions", href: "/" },
{ name: "Action Details" },
]
}
},
{
path: '/diagnostics',
name: 'Diagnostics',
component: () => import('./views/DiagnosticsView.vue'),
meta: {
title: 'Diagnostics',
icon: Wrench01Icon
}
},
{
path: '/login',
name: 'Login',
component: () => import('./views/LoginView.vue'),
meta: { title: 'Login' }
},
{
path: '/user',
name: 'UserInformation',
component: () => import('./views/UserControlPanel.vue'),
meta: { title: 'User Information' }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('./views/NotFoundView.vue'),
meta: { title: 'Page Not Found' }
}
]
// Create router instance
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
// Navigation guard to update page title
router.beforeEach((to, from, next) => {
if (to.meta && to.meta.title) {
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = to.meta.title + " - " + pageTitle
}
next()
})
// Navigation guard for authentication (if needed)
router.beforeEach((to, from, next) => {
// Check if user is authenticated for protected routes
const isAuthenticated = window.isAuthenticated || true // Default to true for now
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
export default router

View File

@@ -0,0 +1,3 @@
import { reactive } from 'vue'
export const buttonResults = reactive({})

View File

@@ -0,0 +1,389 @@
<template>
<Section :title="'Action Details: ' + actionTitle" :padding="false">
<template #toolbar>
<button v-if="action" @click="startAction" title="Start this action" class="button neutral">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="M8 6v12l8-6z" />
</svg>
Start
</button>
</template>
<div class = "flex-row padding" v-if="action">
<div class = "fg1">
<dl>
<dt>Title</dt>
<dd>{{ action.title }}</dd>
<dt>Timeout</dt>
<dd>{{ action.timeout }} seconds</dd>
</dl>
<p v-if="action" class = "fg1">
Execution history for this action. You can filter by execution tracking ID.
</p>
</div>
<div style = "align-self: start; text-align: right;">
<span class="icon" v-html="action.icon"></span>
<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>
<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" />
</svg>
</button>
</label>
</div>
</div>
</div>
<div v-show="filteredLogs.length > 0">
<table class="logs-table">
<thead>
<tr>
<th>Timestamp</th>
<th>Execution ID</th>
<th>Metadata</th>
<th>Status</th>
</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>
<router-link :to="`/logs/${log.executionTrackingId}`">
{{ log.executionTrackingId }}
</router-link>
</td>
<td class="tags">
<span class="annotation">
<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>
</td>
<td class="exit-code">
<span :class="getStatusClass(log) + ' annotation'">
{{ getStatusText(log) }}
</span>
</td>
</tr>
</tbody>
</table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class="padding"
@page-size-change="handlePageSizeChange" itemTitle="execution logs" />
</div>
<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>
</div>
</Section>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import Pagination from 'picocrank/vue/components/Pagination.vue'
import Section from 'picocrank/vue/components/Section.vue'
const route = useRoute()
const router = useRouter()
const logs = ref([])
const action = ref(null)
const actionTitle = ref('Action Details')
const searchText = ref('')
const pageSize = ref(10)
const currentPage = ref(1)
const loading = ref(false)
const totalCount = ref(0)
const filteredLogs = computed(() => {
if (!searchText.value) {
return logs.value
}
const searchLower = searchText.value.toLowerCase()
return logs.value.filter(log =>
log.executionTrackingId.toLowerCase().includes(searchLower) ||
log.actionTitle.toLowerCase().includes(searchLower)
)
})
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)),
}
const response = await window.client.getActionLogs(args)
logs.value = response.logs
const serverPageSize = Number(response.pageSize)
if (Number.isFinite(serverPageSize) && serverPageSize > 0) {
pageSize.value = serverPageSize
}
totalCount.value = Number(response.totalCount) || 0
} catch (err) {
console.error('Failed to fetch action logs:', err)
window.showBigError('fetch-action-logs', 'getting action logs', err, false)
} finally {
loading.value = false
}
}
async function fetchAction() {
try {
const actionId = route.params.actionId
const args = {
"bindingId": actionId
}
const response = await window.client.getActionBinding(args)
action.value = response.action
actionTitle.value = action.value?.title || 'Unknown Action'
} catch (err) {
console.error('Failed to fetch action:', err)
window.showBigError('fetch-action', 'getting action details', err, false)
}
}
function resetState() {
action.value = null
actionTitle.value = 'Action Details'
logs.value = []
totalCount.value = 0
currentPage.value = 1
searchText.value = ''
loading.value = true
}
function clearSearch() {
searchText.value = ''
}
function formatTimestamp(timestamp) {
if (!timestamp) return 'Unknown'
try {
const date = new Date(timestamp)
return date.toLocaleString()
} catch (err) {
return timestamp
}
}
function getStatusClass(log) {
if (log.timedOut) return 'status-timeout'
if (log.blocked) return 'status-blocked'
if (log.exitCode !== 0) return 'status-error'
return 'status-success'
}
function getStatusText(log) {
if (log.timedOut) return 'Timed out'
if (log.blocked) return 'Blocked'
if (log.exitCode !== 0) return `Exit code ${log.exitCode}`
return 'Completed'
}
function handlePageChange(page) {
currentPage.value = page
fetchActionLogs()
}
function handlePageSizeChange(newPageSize) {
pageSize.value = newPageSize
currentPage.value = 1
fetchActionLogs()
}
async function startAction() {
if (!action.value || !action.value.bindingId) {
console.error('Cannot start action: no binding ID')
return
}
try {
const args = {
"bindingId": action.value.bindingId,
"arguments": []
}
const response = await window.client.startAction(args)
router.push(`/logs/${response.executionTrackingId}`)
} catch (err) {
console.error('Failed to start action:', err)
window.showBigError('start-action', 'starting action', err, false)
}
}
onMounted(() => {
fetchAction()
fetchActionLogs()
})
watch(
() => route.params.actionId,
() => {
resetState()
fetchAction()
fetchActionLogs()
},
{ immediate: false }
)
</script>
<style scoped>
.action-header {
display: flex;
align-items: center;
gap: 0.5rem;
}
.action-header h2 {
margin: 0;
}
.icon {
font-size: 1.5rem;
}
.logs-table {
width: 100%;
border-collapse: collapse;
}
.logs-table th {
background-color: var(--section-background);
padding: 0.5rem;
text-align: left;
font-weight: 600;
}
.logs-table td {
padding: 0.5rem;
border-top: 1px solid var(--border-color);
}
.log-row:hover {
background-color: var(--hover-background);
}
.timestamp {
font-family: monospace;
font-size: 0.9rem;
color: var(--text-secondary);
}
.empty-state {
padding: 2rem;
text-align: center;
color: var(--text-secondary);
}
.filter-container {
display: flex;
justify-content: flex-end;
padding: 0.5rem 1rem;
}
.input-with-icons {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
background: var(--section-background);
width: 100%;
max-width: 300px;
}
.input-with-icons input {
border: none;
outline: none;
background: transparent;
flex: 1;
color: var(--text-primary);
}
.input-with-icons button {
background: none;
border: none;
cursor: pointer;
color: var(--text-secondary);
}
.input-with-icons button:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.annotation {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.85rem;
}
.annotation-key {
font-weight: 600;
color: var(--text-secondary);
}
.annotation-val {
color: var(--text-primary);
}
.tag-list {
display: inline-flex;
gap: 0.25rem;
}
.tag {
background-color: var(--accent-color);
color: var(--accent-text);
padding: 0.1rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.85rem;
}
.exit-code .status-success {
color: #28a745;
}
.exit-code .status-error {
color: #dc3545;
}
.exit-code .status-timeout {
color: #ffc107;
}
.exit-code .status-blocked {
color: #6c757d;
}
.padding {
padding: 1rem;
}
</style>

View File

@@ -0,0 +1,408 @@
<template>
<section id = "argument-popup">
<div class="section-header">
<h2>Start action: {{ title }}</h2>
</div>
<div class="section-content padding">
<form @submit="handleSubmit">
<template v-if="actionArguments.length > 0">
<template v-for="arg in actionArguments" :key="arg.name">
<label :for="arg.name">
{{ formatLabel(arg.title) }}
</label>
<datalist v-if="arg.suggestions && Object.keys(arg.suggestions).length > 0" :id="`${arg.name}-choices`">
<option v-for="(suggestion, key) in arg.suggestions" :key="key" :value="key">
{{ suggestion }}
</option>
</datalist>
<select v-if="getInputComponent(arg) === 'select'" :id="arg.name" :name="arg.name" :value="getArgumentValue(arg)"
:required="arg.required" @input="handleInput(arg, $event)" @change="handleChange(arg, $event)">
<option v-for="choice in arg.choices" :key="choice.value" :value="choice.value">
{{ choice.title || choice.value }}
</option>
</select>
<component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name" :value="getArgumentValue(arg)"
:list="arg.suggestions ? `${arg.name}-choices` : 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)" />
<span class="argument-description" v-html="arg.description"></span>
</template>
</template>
<div v-else>
<p>No arguments required</p>
</div>
<div class="buttons">
<button name="start" type="submit" :disabled="hasConfirmation && !confirmationChecked">
Start
</button>
<button name="cancel" type="button" @click="handleCancel">
Cancel
</button>
</div>
</form>
</div>
</section>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// Reactive data
const dialog = ref(null)
const title = ref('')
const icon = ref('')
//const arguments = ref([])
const argValues = ref({})
const confirmationChecked = ref(false)
const hasConfirmation = ref(false)
const formErrors = ref({})
const actionArguments = ref([])
// Computed properties
const props = defineProps({
bindingId: {
type: String,
required: true
}
})
// Methods
async function setup() {
const ret = await window.client.getActionBinding({
bindingId: props.bindingId
})
const action = ret.action
title.value = action.title
icon.value = action.icon
actionArguments.value = action.arguments || []
argValues.value = {}
formErrors.value = {}
confirmationChecked.value = false
hasConfirmation.value = false
// Initialize values from query params or defaults
actionArguments.value.forEach(arg => {
if (arg.type === 'confirmation') {
hasConfirmation.value = true
const paramValue = getQueryParamValue(arg.name)
let checkedValue = false
if (paramValue !== null) {
checkedValue = paramValue === '1' || paramValue === 'true' || paramValue === true
} else if (arg.defaultValue !== undefined && arg.defaultValue !== '') {
checkedValue = arg.defaultValue === '1' || arg.defaultValue === 'true' || arg.defaultValue === true
}
argValues.value[arg.name] = checkedValue
confirmationChecked.value = checkedValue
} else {
const paramValue = getQueryParamValue(arg.name)
argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
}
})
// Run initial validation on all fields after DOM is updated
await nextTick()
for (const arg of actionArguments.value) {
if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation') {
await validateArgument(arg, argValues.value[arg.name])
}
}
}
function getQueryParamValue(paramName) {
const params = new URLSearchParams(window.location.search.substring(1))
return params.get(paramName)
}
function formatLabel(title) {
const lastChar = title.charAt(title.length - 1)
if (lastChar === '?' || lastChar === '.' || lastChar === ':') {
return title
}
return title + ':'
}
function getInputComponent(arg) {
if (arg.type === 'html') {
return 'div'
} else if (arg.type === 'raw_string_multiline') {
return 'textarea'
} else if (arg.choices && arg.choices.length > 0 && (arg.type === 'select' || arg.type === '')) {
return 'select'
} else {
return 'input'
}
}
function getInputType(arg) {
if (arg.type === 'html' || arg.type === 'raw_string_multiline' || arg.type === 'select') {
return undefined
}
if (arg.type === 'confirmation') {
return 'checkbox'
}
if (arg.type === 'ascii_identifier' || arg.type === 'ascii') {
return 'text'
}
if (arg.type === 'datetime') {
return 'datetime-local'
}
return arg.type
}
function getPattern(arg) {
if (arg.type && arg.type.startsWith('regex:')) {
return arg.type.replace('regex:', '')
}
return undefined
}
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 handleInput(arg, event) {
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value
argValues.value[arg.name] = value
updateUrlWithArg(arg.name, value)
}
function handleChange(arg, event) {
if (arg.type === 'confirmation') {
confirmationChecked.value = event.target.checked
return
}
// Validate the input
validateArgument(arg, event.target.value)
}
async function validateArgument(arg, value) {
if (!arg.type || arg.type.startsWith('regex:')) {
return
}
// Skip validation for datetime - backend will handle mangling values without seconds
if (arg.type === 'datetime') {
const inputElement = document.getElementById(arg.name)
if (inputElement) {
inputElement.setCustomValidity('')
}
delete formErrors.value[arg.name]
return
}
try {
const validateArgumentTypeArgs = {
value: value,
type: arg.type
}
const validation = await window.client.validateArgumentType(validateArgumentTypeArgs)
// Get the input element to set custom validity
const inputElement = document.getElementById(arg.name)
if (validation.valid) {
delete formErrors.value[arg.name]
// Clear custom validity message
if (inputElement) {
inputElement.setCustomValidity('')
}
} else {
formErrors.value[arg.name] = validation.description
// Set custom validity message
if (inputElement) {
inputElement.setCustomValidity(validation.description)
}
}
} catch (err) {
console.warn('Validation failed:', err)
// On error, clear any custom validity
const inputElement = document.getElementById(arg.name)
if (inputElement) {
inputElement.setCustomValidity('')
}
}
}
function updateUrlWithArg(name, value) {
if (name && value !== undefined) {
const url = new URL(window.location.href)
// Don't add passwords to URL
const arg = actionArguments.value.find(a => a.name === name)
if (arg && arg.type === 'password') {
return
}
url.searchParams.set(name, value)
window.history.replaceState({}, '', url.toString())
}
}
function getArgumentValues() {
const ret = []
for (const arg of actionArguments.value) {
let value = argValues.value[arg.name] || ''
if (arg.type === 'checkbox' || arg.type === 'confirmation') {
value = value ? '1' : '0'
}
ret.push({
name: arg.name,
value: value
})
}
return ret
}
function getUniqueId() {
if (window.isSecureContext) {
return window.crypto.randomUUID()
} else {
return Date.now().toString()
}
}
async function startAction(actionArgs) {
const startActionArgs = {
bindingId: props.bindingId,
arguments: actionArgs,
uniqueTrackingId: getUniqueId()
}
try {
const response = await window.client.startAction(startActionArgs)
console.log('Action started successfully with tracking ID:', response.executionTrackingId)
return response
} catch (err) {
console.error('Failed to start action:', err)
throw err
}
}
async function handleSubmit(event) {
// Set custom validity for required fields
for (const arg of actionArguments.value) {
const value = argValues.value[arg.name]
const inputElement = document.getElementById(arg.name)
if (arg.required && (!value || value === '')) {
formErrors.value[arg.name] = 'This field is required'
// Set custom validity for required field validation
if (inputElement) {
inputElement.setCustomValidity('This field is required')
}
}
}
const form = event.target
if (!form.checkValidity()) {
console.log('argument form has elements that failed validation')
return
}
event.preventDefault()
const argvs = getArgumentValues()
console.log('argument form has elements that passed validation')
try {
const response = await startAction(argvs)
router.push(`/logs/${response.executionTrackingId}`)
} catch (err) {
console.error('Failed to start action:', err)
}
}
function handleCancel() {
router.back()
clearBookmark()
}
function clearBookmark() {
window.history.replaceState({
path: window.location.pathname
}, '', window.location.pathname)
}
function show() {
if (dialog.value) {
dialog.value.showModal()
}
}
function close() {
if (dialog.value) {
dialog.value.close()
}
}
// Expose methods for parent components
defineExpose({
show,
close
})
// Lifecycle
onMounted(() => {
setup()
})
</script>
<style scoped>
form {
grid-template-columns: max-content auto auto;
}
.argument-description {
font-size: 0.875rem;
color: #666;
margin-top: 0.25rem;
}
.buttons {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
padding-top: 1rem;
border-top: 1px solid #eee;
}
/* Checkbox specific styling */
.argument-group input[type="checkbox"] {
width: auto;
margin-right: 0.5rem;
}
.argument-group input[type="checkbox"]+label {
display: inline;
font-weight: normal;
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<Section :title="t('diagnostics.get-support')">
<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>
</li>
</ul>
</Section>
<Section :title="t('diagnostics.ssh')">
<dl>
<dt>{{ t('diagnostics.found-key') }}</dt>
<dd>{{ diagnostics.sshFoundKey || '?' }}</dd>
<dt>{{ t('diagnostics.found-config') }}</dt>
<dd>{{ diagnostics.sshFoundConfig || '?' }}</dd>
</dl>
</Section>
<Section :title="t('diagnostics.sos-report')">
<p>{{ t('diagnostics.sos-report-description') }}</p>
<p>
<a href="https://docs.olivetin.app/troubleshooting/sosreport.html" target="_blank">{{ t('diagnostics.sos-report-docs') }}</a>
</p>
<div role="toolbar">
<button @click="generateSosReport" :disabled="loading" class = "good">{{ t('diagnostics.generate-sos-report') }}</button>
<button @click="copySosReport" :disabled="!sosReport || loading" :class="sosReportCopied ? 'good' : ''">{{ sosReportCopied ? t('diagnostics.copied') : t('diagnostics.copy-to-clipboard') }}</button>
</div>
<textarea v-model="sosReport" readonly style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"></textarea>
</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>
</div>
<textarea v-model="browserInfo" readonly style="flex: 1; min-height: 200px; resize: vertical; width: 100%; box-sizing: border-box;"></textarea>
</Section>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Section from 'picocrank/vue/components/Section.vue'
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
const diagnostics = ref({})
const loading = ref(false)
const sosReport = ref('')
const browserInfo = ref('')
const sosReportCopied = ref(false)
const browserInfoCopied = ref(false)
async function fetchDiagnostics() {
loading.value = true
try {
const response = await window.client.getDiagnostics();
diagnostics.value = {
sshFoundKey: response.SshFoundKey,
sshFoundConfig: response.SshFoundConfig
};
} catch (err) {
console.error('Failed to fetch diagnostics:', err);
diagnostics.value = {
sshFoundKey: t('diagnostics.unknown'),
sshFoundConfig: t('diagnostics.unknown')
}
}
loading.value = false
}
function formatKey(key) {
return key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase())
.trim()
}
async function generateSosReport() {
const response = await window.client.sosReport()
console.log("response", response)
sosReport.value = `\`\`\`\n${response.alert}\n\`\`\`\n`
}
async function copySosReport() {
try {
await navigator.clipboard.writeText(sosReport.value)
sosReportCopied.value = true
setTimeout(() => {
sosReportCopied.value = false
}, 2000)
} catch (err) {
console.error('Failed to copy SOS report to clipboard:', err)
}
}
async function generateBrowserInfo() {
loading.value = true
try {
let userAgentData = 'N/A'
if (navigator.userAgentData) {
try {
const uaData = await navigator.userAgentData.getHighEntropyValues([
'platform',
'platformVersion',
'architecture',
'model',
'uaFullVersion',
'bitness',
'fullVersionList'
])
userAgentData = JSON.stringify(uaData, null, 2)
} catch (err) {
userAgentData = `${t('diagnostics.useragent-data-error')}: ${err.message}`
}
}
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
languages: navigator.languages?.join(', ') || 'N/A',
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
screenWidth: screen.width,
screenHeight: screen.height,
screenColorDepth: screen.colorDepth,
screenPixelDepth: screen.pixelDepth,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
devicePixelRatio: window.devicePixelRatio || 'N/A',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timezoneOffset: new Date().getTimezoneOffset(),
localStorageEnabled: (() => {
try {
localStorage.setItem('test', 'test')
localStorage.removeItem('test')
return true
} catch {
return false
}
})(),
sessionStorageEnabled: (() => {
try {
sessionStorage.setItem('test', 'test')
sessionStorage.removeItem('test')
return true
} catch {
return false
}
})(),
hardwareConcurrency: navigator.hardwareConcurrency || 'N/A',
maxTouchPoints: navigator.maxTouchPoints || 'N/A',
userAgentData: userAgentData
}
const olivetinVersion = window.initResponse?.currentVersion || t('diagnostics.unknown')
const currentLanguage = locale.value || t('diagnostics.unknown')
let output = '';
output += `\`\`\`\n`
output += '### BROWSER INFO START (copy all text to BROWSER INFO END)\n'
output += `# OliveTin Information\n`
output += `olivetinVersion: ${olivetinVersion}\n`
output += `currentLanguage: ${currentLanguage}\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 += `userAgentData:\n${info.userAgentData}\n`
output += `\n# Display Information\n`
output += `screenWidth: ${info.screenWidth}\n`
output += `screenHeight: ${info.screenHeight}\n`
output += `screenColorDepth: ${info.screenColorDepth}\n`
output += `screenPixelDepth: ${info.screenPixelDepth}\n`
output += `viewportWidth: ${info.viewportWidth}\n`
output += `viewportHeight: ${info.viewportHeight}\n`
output += `devicePixelRatio: ${info.devicePixelRatio}\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 += `timezone: ${info.timezone}\n`
output += `timezoneOffset: ${info.timezoneOffset}\n`
output += `\n### BROWSER INFO END (copy all text from BROWSER INFO START)`
output += `\n\`\`\`\n`
browserInfo.value = output
} finally {
loading.value = false
}
}
async function copyBrowserInfo() {
try {
await navigator.clipboard.writeText(browserInfo.value)
browserInfoCopied.value = true
setTimeout(() => {
browserInfoCopied.value = false
}, 2000)
} catch (err) {
console.error('Failed to copy browser info to clipboard:', err)
}
}
onMounted(() => {
fetchDiagnostics()
})
</script>
<style scoped>
.diagnostics-view {
padding: 1rem;
}
.diagnostics-content {
max-width: 800px;
margin: 0 auto;
}
.note {
background: #f8f9fa;
border-left: 4px solid #007bff;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0 4px 4px 0;
font-size: 0.875rem;
color: #495057;
}
.note a {
color: #007bff;
text-decoration: none;
}
.note a:hover {
text-decoration: underline;
}
.diagnostics-table {
width: 100%;
border-collapse: collapse;
}
.diagnostics-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid #f1f3f4;
}
.diagnostics-table td:first-child {
font-weight: 500;
color: #495057;
background: #f8f9fa;
}
.diagnostics-table tr:last-child td {
border-bottom: none;
}
.error-list {
padding: 1rem;
}
.error-item {
background: #f8d7da;
color: #721c24;
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 4px;
border-left: 4px solid #dc3545;
font-family: monospace;
font-size: 0.875rem;
}
.error-item:last-child {
margin-bottom: 0;
}
.flex-col {
display: flex;
flex-direction: column;
}
.section-content {
display: flex;
flex-direction: column;
gap: 1em;
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<Section class = "with-header-and-content" v-if="entityDefinitions.length === 0" title="Loading entity definitions...">
<div class = "section-header">
<h2 class="loading-message">
Loading entity definitions...
</h2>
</div>
</Section>
<template v-else>
<Section v-for="def in entityDefinitions" :key="def.title" :title="'Entity: ' + def.title ">
<div class = "section-content">
<p>{{ def.instances.length }} instances.</p>
<ul>
<li v-for="inst in def.instances" :key="inst.uniqueKey">
<router-link :to="{ name: 'EntityDetails', params: { entityType: inst.type, entityKey: inst.uniqueKey } }">
{{ inst.title }}
</router-link>
</li>
</ul>
<h3>Used on Dashboards:</h3>
<ul>
<li v-for="dash in def.usedOnDashboards">
<router-link :to="{ name: 'Dashboard', params: { title: dash } }">
{{ dash }}
</router-link>
</li>
</ul>
</div>
</Section>
</template>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Section from 'picocrank/vue/components/Section.vue'
const entityDefinitions = ref([])
async function fetchEntities() {
const ret = await window.client.getEntities()
entityDefinitions.value = ret.entityDefinitions
}
onMounted(() => {
fetchEntities()
})
</script>

View File

@@ -0,0 +1,163 @@
<template>
<Section title="Entity Details">
<template #toolbar>
<button @click="goBack" class="back-button">
<HugeiconsIcon :icon="ArrowLeftIcon" width="1.2em" height="1.2em" />
<span>Back</span>
</button>
</template>
<div v-if="!entityDetails">
<p>Loading entity details...</p>
</div>
<template v-else>
<dl>
<dt>Type</dt>
<dd>
<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>
</dl>
<p v-if="!entityDetails.title">No details available for this entity.</p>
<hr />
<h3>Dashboard Entity Directories</h3>
<div v-if="entityDetails.directories && entityDetails.directories.length > 0" class="directories-section">
<ul class="directory-list">
<li v-for="directory in entityDetails.directories" :key="directory">
<router-link
:to="{
name: 'Dashboard',
params: {
title: directory,
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">Learn more</a>
</p>
</template>
</Section>
</template>
<script setup>
import { ref, 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'
const router = useRouter()
const entityDetails = ref(null)
const props = defineProps({
entityType: String,
entityKey: String
})
function goBack() {
router.push({ name: 'Entities' })
}
async function fetchEntityDetails() {
try {
const response = await window.client.getEntity({
type: props.entityType,
uniqueKey: props.entityKey
})
entityDetails.value = response
} catch (err) {
console.error('Failed to fetch entity details:', err)
window.showBigError('fetch-entity-details', 'getting entity details', err, false)
}
}
onMounted(() => {
fetchEntityDetails()
})
</script>
<style scoped>
.back-button {
display: flex;
align-items: center;
gap: 0.5em;
padding: 0.5em 1em;
background-color: var(--bg, #fff);
border: 1px solid var(--border-color, #ccc);
border-radius: 0.5em;
cursor: pointer;
font-size: 0.9em;
box-shadow: 0 0 .3em rgba(0, 0, 0, 0.1);
transition: background-color 0.2s, box-shadow 0.2s;
}
.back-button:hover {
background-color: var(--bg-hover, #f5f5f5);
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.15);
}
.directories-section h3 {
margin-bottom: 0.5em;
font-size: 1.1em;
}
.directory-list a {
text-decoration: none;
padding: 0.5em;
display: inline-block;
border-radius: 0.3em;
transition: background-color 0.2s;
}
.directory-list a:hover {
background-color: var(--bg-hover, #f5f5f5);
text-decoration: underline;
}
.entity-type-link {
text-decoration: none;
transition: opacity 0.2s;
}
.entity-type-link:hover {
text-decoration: underline;
opacity: 0.8;
}
hr {
border: 0;
border-top: 1px solid var(--border-color, #ccc);
}
@media (prefers-color-scheme: dark) {
.back-button {
background-color: var(--bg, #111);
border-color: var(--border-color, #333);
}
.back-button:hover {
background-color: var(--bg-hover, #222);
}
.directories-section {
border-top-color: var(--border-color, #333);
}
.directory-list a:hover {
background-color: var(--bg-hover, #222);
}
}
</style>

View File

@@ -0,0 +1,413 @@
<template>
<Section :title="'Execution Results: ' + title" id = "execution-results-popup">
<template #toolbar>
<router-link v-if="actionId" :to="`/action/${actionId}`" title="View all executions for this action" class="button neutral">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm.31-8.86c-1.77-.45-2.34-.94-2.34-1.67 0-.84.79-1.43 2.1-1.43 1.38 0 1.9.66 1.94 1.64h1.71c-.05-1.34-.87-2.57-2.49-2.97V5H10.9v1.69c-1.51.32-2.72 1.3-2.72 2.81 0 1.79 1.49 2.69 3.66 3.21 1.95.46 2.34 1.22 2.34 1.8 0 .53-.39 1.39-2.1 1.39-1.6 0-2.05-.56-2.13-1.45H8.04c.08 1.5 1.18 2.37 2.82 2.69V19h2.34v-1.63c1.65-.35 2.48-1.24 2.48-2.77-.01-1.88-1.51-2.87-3.7-3.23z"/>
</svg>
Action Details
</router-link>
<button @click="toggleSize" title="Toggle dialog size" class = "neutral">
<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>
</template>
<div v-if="logEntry" class = "flex-row">
<dl class = "fg1">
<dt>Duration</dt>
<dd><span v-html="duration"></span></dd>
<dt>Status</dt>
<dd>
<ActionStatusDisplay :log-entry="logEntry" id = "execution-dialog-status" />
</dd>
</dl>
<span class="icon" role="img" v-html="icon" style = "align-self: start"></span>
</div>
<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>.
</div>
<div ref="xtermOutput"></div>
<br />
<div class="flex-row g1 buttons padded-content">
<button @click="goBack" title="Go back">
<HugeiconsIcon :icon="ArrowLeftIcon" />
Back
</button>
<div class = "fg1" />
<button :disabled="!canRerun" @click="rerunAction" title="Rerun">
<HugeiconsIcon :icon="WorkoutRunIcon" />
Rerun
</button>
<button :disabled="!canKill" @click="killAction" title="Kill" id = "execution-dialog-kill-action">
<HugeiconsIcon :icon="Cancel02Icon" />
Kill
</button>
</div>
</Section>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
import Section from 'picocrank/vue/components/Section.vue'
import { OutputTerminal } from '../../../js/OutputTerminal.js'
import { HugeiconsIcon } from '@hugeicons/vue'
import { WorkoutRunIcon, Cancel02Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
import { useRouter } from 'vue-router'
import { buttonResults } from '../stores/buttonResults'
const router = useRouter()
// Refs for DOM elements
const xtermOutput = ref(null)
const props = defineProps({
executionTrackingId: {
type: String,
required: true
}
})
const executionTrackingId = ref(props.executionTrackingId)
const hideBasics = ref(false)
const hideDetails = ref(false)
const hideDetailsOnResult = ref(false)
const executionSeconds = ref(0)
const icon = ref('')
const title = ref('Waiting for result...')
const titleTooltip = ref('')
const duration = ref('')
const logEntry = ref(null)
const canRerun = ref(false)
const canKill = ref(false)
const actionId = ref('')
const notFound = ref(false)
const errorMessage = ref('')
let executionTicker = null
let terminal = null
function initializeTerminal() {
terminal = new OutputTerminal(executionTrackingId.value)
terminal.open(xtermOutput.value)
terminal.resize(80, 40)
window.terminal = terminal
}
function toggleSize() {
if (!xtermOutput.value) {
return
}
if (xtermOutput.value.requestFullscreen) {
xtermOutput.value.requestFullscreen()
} else if (xtermOutput.value.webkitRequestFullscreen) {
xtermOutput.value.webkitRequestFullscreen()
} else if (xtermOutput.value.mozRequestFullScreen) {
xtermOutput.value.mozRequestFullScreen()
} else if (xtermOutput.value.msRequestFullscreen) {
xtermOutput.value.msRequestFullscreen()
}
}
async function reset() {
executionSeconds.value = 0
executionTrackingId.value = 'notset'
hideBasics.value = false
hideDetails.value = false
hideDetailsOnResult.value = false
icon.value = ''
title.value = 'Waiting for result...'
titleTooltip.value = ''
duration.value = ''
canRerun.value = false
canKill.value = false
logEntry.value = null
notFound.value = false
errorMessage.value = ''
if (terminal) {
await terminal.reset()
terminal.fit()
}
}
function show(actionButton) {
if (actionButton) {
icon.value = actionButton.domIcon.innerText
}
canKill.value = true
// Clear existing ticker
if (executionTicker) {
clearInterval(executionTicker)
}
executionSeconds.value = 0
executionTick()
executionTicker = setInterval(() => {
executionTick()
}, 1000)
}
async function rerunAction() {
if (!logEntry.value || !logEntry.value.actionId) {
console.error('Cannot rerun: no action ID available')
return
}
try {
const startActionArgs = {
"bindingId": logEntry.value.actionId,
"arguments": []
}
const res = await window.client.startAction(startActionArgs)
router.push(`/logs/${res.executionTrackingId}`)
} catch (err) {
console.error('Failed to rerun action:', err)
window.showBigError('rerun-action', 'rerunning action', err, false)
}
}
async function killAction() {
if (!executionTrackingId.value || executionTrackingId.value === 'notset') {
return
}
const killActionArgs = {
executionTrackingId: executionTrackingId.value
}
try {
await window.client.killAction(killActionArgs)
} catch (err) {
console.error('Failed to kill action:', err)
}
}
function executionTick() {
executionSeconds.value++
updateDuration(null)
}
function hideEverythingApartFromOutput() {
hideDetailsOnResult.value = true
hideBasics.value = true
hideDetailsOnResult.value = true
hideBasics.value = true
}
async function fetchExecutionResult(executionTrackingIdParam) {
console.log("fetchExecutionResult", executionTrackingIdParam)
executionTrackingId.value = executionTrackingIdParam
notFound.value = false
errorMessage.value = ''
const executionStatusArgs = {
executionTrackingId: executionTrackingId.value
}
try {
const logEntryResult = await window.client.executionStatus(executionStatusArgs)
await renderExecutionResult(logEntryResult)
} catch (err) {
// Check if it's a "not found" error (404 or similar)
if (err.status === 404 || err.code === 'NotFound' || err.message?.includes('not found')) {
notFound.value = true
errorMessage.value = err.message || 'The execution could not be found in the system.'
} else {
renderError(err)
}
throw err
}
}
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) {
duration.value = logEntry.value.datetimeStarted
} else {
let delta = ''
try {
delta = (new Date(logEntry.value.datetimeFinished) - new Date(logEntry.value.datetimeStarted)) / 1000
delta = new Intl.RelativeTimeFormat().format(delta, 'seconds').replace('in ', '').replace('ago', '')
} catch (e) {
console.warn('Failed to calculate delta', e)
}
duration.value = logEntry.value.datetimeStarted + ' &rarr; ' + logEntry.value.datetimeFinished
if (delta !== '') {
duration.value += ' (' + delta + ')'
}
}
}
async function renderExecutionResult(res) {
logEntry.value = res.logEntry
// Clear ticker
if (executionTicker) {
clearInterval(executionTicker)
}
executionTicker = null
if (hideDetailsOnResult.value) {
hideDetails.value = true
}
executionTrackingId.value = res.logEntry.executionTrackingId
canRerun.value = res.logEntry.executionFinished
canKill.value = res.logEntry.canKill
icon.value = res.logEntry.actionIcon
title.value = res.logEntry.actionTitle
titleTooltip.value = 'Action ID: ' + res.logEntry.actionId + '\nExecution ID: ' + res.logEntry.executionTrackingId
actionId.value = res.logEntry.actionId
updateDuration(res.logEntry)
if (terminal) {
await terminal.reset()
await terminal.write(res.logEntry.output, () => {
terminal.fit()
})
}
}
function renderError(err) {
window.showBigError('execution-dlg-err', 'in the execution dialog', 'Failed to fetch execution result. ' + err, false)
}
function handleClose() {
if (executionTicker) {
clearInterval(executionTicker)
}
executionTicker = null
}
function cleanup() {
if (executionTicker) {
clearInterval(executionTicker)
}
executionTicker = null
if (terminal != null) {
terminal.close()
}
terminal = null
}
function goBack() {
router.back()
}
onMounted(() => {
document.addEventListener('fullscreenchange', (e) => {
setTimeout(() => { // Wait for the DOM to settle
if (document.fullscreenElement) {
terminal.fit()
} else {
terminal.resize(80, 40)
terminal.fit()
}
}, 100)
})
initializeTerminal()
fetchExecutionResult(props.executionTrackingId)
watch(
() => buttonResults[props.executionTrackingId],
(newResult, oldResult) => {
if (newResult) {
renderExecutionResult({
logEntry: newResult
})
}
}
)
})
onBeforeUnmount(() => {
cleanup()
})
// Expose methods for parent/imperative use
defineExpose({
reset,
show,
rerunAction,
killAction,
fetchExecutionResult,
renderExecutionResult,
hideEverythingApartFromOutput,
handleClose
})
</script>
<style scoped>
.action-history-link {
color: var(--link-color, #007bff);
text-decoration: none;
display: inline-block;
font-size: 0.9rem;
}
.error-message {
background-color: #f8d7da;
border: 1px solid #f5c2c7;
border-radius: 0.25rem;
padding: 1.5rem;
margin: 1rem 0;
}
.error-message h3 {
margin: 0 0 0.5rem 0;
color: #721c24;
}
.error-message p {
margin: 0.5rem 0;
color: #721c24;
}
.error-message code {
background-color: #f8d7da;
padding: 0.125rem 0.25rem;
border-radius: 0.125rem;
font-family: monospace;
}
.error-message a {
color: #721c24;
text-decoration: underline;
font-weight: 500;
}
</style>

View File

@@ -0,0 +1,153 @@
<template>
<Section title="Login to OliveTin" class="small">
<div class="login-form">
<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">
<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>
<span class="provider-name">Login with {{ provider.title }}</span>
</button>
</div>
</div>
<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">
{{ 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 />
<button type="submit" :disabled="loading" class="login-button">
{{ loading ? 'Logging in...' : 'Login' }}
</button>
</form>
</div>
</div>
</Section>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import Section from 'picocrank/vue/components/Section.vue'
const router = useRouter()
const username = ref('')
const password = ref('')
const loading = ref(false)
const loginError = ref('')
const hasOAuth = ref(false)
const hasLocalLogin = ref(false)
const oauthProviders = ref([])
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
hasLocalLogin.value = window.initResponse.authLocalLogin
if (hasOAuth.value) {
oauthProviders.value = window.initResponse.oAuth2Providers
}
} else {
console.warn('Init response not available yet, login options will be empty')
}
}
async function handleLocalLogin() {
loading.value = true
loginError.value = ''
try {
const response = await window.client.localUserLogin({
username: username.value,
password: password.value
})
if (response.success) {
// Re-initialize to get updated user context
try {
const initResponse = await window.client.init({})
window.initResponse = initResponse
window.initError = false
window.initErrorMessage = ''
window.initCompleted = true
// Update the header with new user info
if (window.updateHeaderFromInit) {
window.updateHeaderFromInit()
}
} catch (initErr) {
console.error('Failed to reinitialize after login:', initErr)
}
// Redirect to home page on successful login
router.push('/')
} else {
loginError.value = 'Login failed. Please check your credentials.'
}
} catch (err) {
console.error('Login error:', err)
loginError.value = err.message || 'Network error. Please try again.'
} finally {
loading.value = false
}
}
function loginWithOAuth(provider) {
if (!provider.key) {
console.error('OAuth provider missing key:', provider)
return
}
const providerKey = encodeURIComponent(provider.key)
window.location.href = `/oauth/login?provider=${providerKey}`
}
onMounted(() => {
loadLoginOptions()
// Also watch for when init response becomes available
const stopWatcher = watch(() => window.initResponse, () => {
loadLoginOptions()
}, { immediate: true })
})
</script>
<style scoped>
section {
margin: auto;
}
.login-view {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
form {
grid-template-columns: 1fr;
gap: 1em;
}
.provider-icon {
width: 1em;
height: 1em;
margin-right: .4em;
display: inline-flex;
vertical-align: middle;
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<Section :title="t('logs.title')" :padding="false">
<template #toolbar>
<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>
<input :placeholder="t('search-filter')" v-model="searchText" />
<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>
</template>
<p class = "padding">{{ t('logs.page-description') }}</p>
<div v-show="filteredLogs.length > 0">
<table class="logs-table">
<thead>
<tr>
<th>{{ t('logs.timestamp') }}</th>
<th>{{ t('logs.action') }}</th>
<th>{{ t('logs.metadata') }}</th>
<th>{{ t('logs.status') }}</th>
</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>
<span class="icon" v-html="log.actionIcon"></span>
<router-link :to="`/logs/${log.executionTrackingId}`">
{{ log.actionTitle }}
</router-link>
</td>
<td class="tags">
<span class="annotation">
<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>
</td>
<td class="exit-code">
<ActionStatusDisplay :logEntry="log" />
</td>
</tr>
</tbody>
</table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class = "padding"
@page-size-change="handlePageSizeChange" itemTitle="execution logs" />
</div>
<div v-show="logs.length === 0" class="empty-state">
<p>{{ t('logs.no-logs-to-display') }}</p>
<router-link to="/">{{ t('return-to-index') }}</router-link>
</div>
</Section>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import Pagination from 'picocrank/vue/components/Pagination.vue'
import Section from 'picocrank/vue/components/Section.vue'
import { useI18n } from 'vue-i18n'
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
const logs = ref([])
const searchText = ref('')
const pageSize = ref(10)
const currentPage = ref(1)
const loading = ref(false)
const totalCount = ref(0)
const { t } = useI18n()
const filteredLogs = computed(() => {
let result = logs.value
if (searchText.value) {
const searchLower = searchText.value.toLowerCase()
result = logs.value.filter(log =>
log.actionTitle.toLowerCase().includes(searchLower)
)
}
// Sort by timestamp with most recent first
return [...result].sort((a, b) => {
const dateA = a.datetimeStarted ? new Date(a.datetimeStarted).getTime() : 0
const dateB = b.datetimeStarted ? new Date(b.datetimeStarted).getTime() : 0
return dateB - dateA // Descending order (most recent first)
})
})
async function fetchLogs() {
loading.value = true
try {
const startOffset = (currentPage.value - 1) * pageSize.value
const args = {
"startOffset": BigInt(startOffset),
}
const response = await window.client.getLogs(args)
logs.value = response.logs
pageSize.value = Number(response.pageSize) || 0
totalCount.value = Number(response.totalCount) || 0
} catch (err) {
console.error('Failed to fetch logs:', err)
window.showBigError('fetch-logs', 'getting logs', err, false)
} finally {
loading.value = false
}
}
function clearSearch() {
searchText.value = ''
}
function formatTimestamp(timestamp) {
if (!timestamp) return 'Unknown'
try {
const date = new Date(timestamp)
return date.toLocaleString()
} catch (err) {
return timestamp
}
}
function handlePageChange(page) {
currentPage.value = page
fetchLogs()
}
function handlePageSizeChange(newPageSize) {
pageSize.value = newPageSize
currentPage.value = 1 // Reset to first page
}
onMounted(() => {
fetchLogs()
})
</script>
<style scoped>
.logs-view {
padding: 1rem;
}
.input-with-icons {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
background: var(--section-background);
width: 100%;
max-width: 300px;
}
.input-with-icons input {
border: none;
outline: none;
background: transparent;
flex: 1;
color: var(--text-primary);
}
.input-with-icons button {
background: none;
border: none;
cursor: pointer;
padding: 0.25rem;
border-radius: 3px;
}
.input-with-icons button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.timestamp {
font-family: monospace;
font-size: 0.875rem;
color: #666;
}
.icon {
margin-right: 0.5rem;
font-size: 1.2em;
}
.content {
color: #007bff;
text-decoration: none;
cursor: pointer;
}
.content:hover {
text-decoration: underline;
}
.annotation {
font-weight: 500;
font-size: smaller;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #666;
}
.empty-state a {
color: #007bff;
text-decoration: none;
}
.empty-state a:hover {
text-decoration: underline;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="not-found-view">
<div class="not-found-container">
<div class="not-found-content">
<h1>404</h1>
<h2>Page Not Found</h2>
<div class="actions">
<button class = "button good" @click="goToHome">
Go to Home
</button>
<button class="button neutral" @click="goBack">
Go Back
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NotFoundView',
methods: {
goBack() {
this.$router.go(-1)
},
goToHome() {
this.$router.push('/')
}
}
}
</script>
<style scoped>
.not-found-content {
padding: 3rem 2rem;
text-align: center;
}
.not-found-content h1 {
font-size: 6rem;
margin: 0;
font-weight: 700;
line-height: 1;
}
.not-found-content h2 {
font-size: 2rem;
margin: 0 0 1rem 0;
color: #333;
}
.not-found-content p {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
}
</style>

View File

@@ -0,0 +1,178 @@
<template>
<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>
</div>
<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>
<dd v-if="acls && acls.length > 0">
<span class="acl-tag" v-for="(acl, idx) in acls" :key="`acl-${idx}`">{{ acl }}</span>
</dd>
</dl>
<div class="user-actions">
<div class="action-buttons">
<button @click="handleLogout" class="button bad" :disabled="loggingOut">
{{ loggingOut ? 'Logging out...' : 'Logout' }}
</button>
</div>
</div>
</div>
</Section>
</template>
<script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import Section from 'picocrank/vue/components/Section.vue'
const router = useRouter()
const isLoggedIn = ref(false)
const username = ref('guest')
const userProvider = ref('system')
const usergroup = ref('')
const loggingOut = ref(false)
const acls = ref([])
function updateUserInfo() {
if (window.initResponse) {
isLoggedIn.value = window.initResponse.authenticatedUser !== '' && window.initResponse.authenticatedUser !== 'guest'
username.value = window.initResponse.authenticatedUser
userProvider.value = window.initResponse.authenticatedUserProvider || 'system'
usergroup.value = window.initResponse.effectivePolicy?.usergroup || ''
}
}
async function fetchWhoAmI() {
try {
const res = await window.client.whoAmI({})
acls.value = res.acls || []
// Update usergroup from authoritative WhoAmI response
if (res.usergroup) {
usergroup.value = res.usergroup
}
} catch (e) {
console.warn('Failed to fetch WhoAmI for ACLs', e)
acls.value = []
}
}
async function handleLogout() {
loggingOut.value = true
try {
await window.client.logout({})
// Re-initialize to get updated user context (should be guest)
try {
const initResponse = await window.client.init({})
window.initResponse = initResponse
window.initError = false
window.initErrorMessage = ''
window.initCompleted = true
// Update the header with new user info
if (window.updateHeaderFromInit) {
window.updateHeaderFromInit()
}
} catch (initErr) {
console.error('Failed to reinitialize after logout:', initErr)
}
// Redirect based on init response: if login is required, go to login page
if (window.initResponse && window.initResponse.loginRequired) {
router.push('/login')
} else {
router.push('/')
}
} catch (err) {
console.error('Logout error:', err)
} finally {
loggingOut.value = false
}
}
let watchInterval = null
onMounted(() => {
updateUserInfo()
fetchWhoAmI()
})
onUnmounted(() => {
if (watchInterval) {
clearInterval(watchInterval)
}
})
</script>
<style scoped>
section {
margin: auto;
}
.user-not-logged-in {
padding: 2rem;
text-align: center;
}
.user-not-logged-in p {
margin: 1rem 0;
}
.user-control-panel {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.action-buttons {
display: flex;
gap: 1rem;
}
.acl-tag {
display: inline-block;
background: var(--section-background);
border: 1px solid var(--border-color);
border-radius: 0.25rem;
padding: 0.1rem 0.4rem;
margin: 0 0.25rem 0.25rem 0;
font-size: 0.85rem;
}
.button {
padding: 0.75rem 1.5rem;
border-radius: 4px;
border: none;
cursor: pointer;
text-align: center;
font-weight: 500;
transition: background-color 0.2s;
}
.button.bad {
background-color: #dc3545;
color: white;
}
.button.bad:hover:not(:disabled) {
background-color: #c82333;
}
.button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>

39
frontend/style.css Normal file
View File

@@ -0,0 +1,39 @@
header {
position: fixed;
width: 100%;
z-index: 5;
}
aside {
padding-top: 4em;
z-index: 3; /* Make sure the sidebar is on top of the terminal */
}
main {
padding-top: 4em;
}
dialog {
border-radius: 1em;
}
section {
padding: 0;
}
aside .flex-row {
padding-left: 1em;
padding-right: .5em;
}
#sidebar-toggler-button {
margin-right: .5em;
}
div.buttons button svg {
vertical-align: middle;
}
section.small {
border-radius: .4em;
}

24
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,24 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({
dirs: ['resources/vue/'],
extensions: ['vue'],
deep: true,
dts: false,
}),
vue(),
],
server: {
proxy: {
'/api': {
target: 'http://localhost:1337',
changeOrigin: true,
secure: false,
}
},
},
})

110
go.mod
View File

@@ -1,110 +0,0 @@
module github.com/OliveTin/OliveTin
go 1.18
require (
github.com/bufbuild/buf v1.26.0
github.com/fsnotify/fsnotify v1.6.0
github.com/fzipp/gocyclo v0.6.0
github.com/go-critic/go-critic v0.8.2
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577
google.golang.org/grpc v1.57.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/bufbuild/connect-go v1.10.0 // indirect
github.com/bufbuild/connect-opentelemetry-go v0.4.0 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cristalhq/acmd v0.11.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v24.0.5+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.5+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/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/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quasilyte/go-ruleguard v0.3.19 // 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/rs/cors v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tetratelabs/wazero v1.4.0 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

672
go.sum
View File

@@ -1,672 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/bufbuild/buf v1.26.0 h1:rB8pErEkxdUNhVZ3+ClFEoJRaa+o2FjEecCWL2S3Qs4=
github.com/bufbuild/buf v1.26.0/go.mod h1:UMPncXMWgrmIM+0QpwTEwjNr2SA0z2YIVZZsmNflvB4=
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
github.com/bufbuild/connect-opentelemetry-go v0.4.0 h1:6JAn10SNqlQ/URhvRNGrIlczKw1wEXknBUUtmWqOiak=
github.com/bufbuild/connect-opentelemetry-go v0.4.0/go.mod h1:nwPXYoDOoc2DGyKE/6pT1Q9MPSi2Et2e6BieMD0l6WU=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/cristalhq/acmd v0.11.1 h1:DJ4fh2Pv0nPKmqT646IU/0Vh5FNdGblxvF+3/W3NAUI=
github.com/cristalhq/acmd v0.11.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
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/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-critic/go-critic v0.8.2 h1:mekhZ9jw5NBEj3I8o/EywXw5zBfGAJuMo4VVVjtxF80=
github.com/go-critic/go-critic v0.8.2/go.mod h1:nZPlrtVfOuLOe8GpvWTfcMzfkG0QVZWAziAeXpivfQo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw=
github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=
github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc=
github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw=
github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tetratelabs/wazero v1.4.0 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819 h1:qdPQGUfaPzFgEbhQCulaXk+s0ETcox/vQd+f9YCIuxY=
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 h1:Tyk/35yqszRCvaragTn5NnkY6IiKk/XvHzEWepo71N0=
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 h1:xv8KoglAClYGkprUSmDTKaILtzfD8XzG9NYVXMprjKo=
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@@ -1,2 +1,4 @@
---
recursive: true
require:
- mochaSetup.mjs

View File

@@ -1,5 +1,24 @@
default:
default: test-install test-run
test-install:
npm install --no-fund
./node_modules/.bin/mocha
test-run:
# GitHub Actions fails badly on the default timeout of 2000ms
npx mocha tests --recursive -t 10000
find-flakey-tests:
echo "Running test-run infinately"
sh -c "while make test-run; do :; done"
nginx:
podman-compose up -d nginx
clean:
podman-compose down
getsnapshot:
rm -rf /opt/OliveTin-snapshot/*
gh run download -D /opt/OliveTin-snapshot/
.PHONY: default

View File

@@ -1 +1,14 @@
# OliveTin-integration-tests
## GitHub Actions (Ubuntu, Local Process)
- `mocha` is run with the default runner that starts and stops OliveTin as a local process (ie, localhost:1337).
## Running different configurations (Local Process, VM, Container)
- Get the snapshot you want to test `make getsnapshot`
- To test against VMs:
-- `export OLIVETIN_TEST_RUNNER=vm`
-- `vagrant up fedora38` (or whatever distro you like defined in `Vagrantfile`)
-- `. envVagrant.sh fedora38` to set the $IP and $PORT
- `mocha`

View File

@@ -3,33 +3,35 @@
Vagrant.configure("2") do |config|
config.vm.box = "generic/centos8"
config.vm.provision "shell", inline: "mkdir /etc/OliveTin && chmod o+w /etc/OliveTin/", privileged: true
config.vm.provision "file", source: "configs/config.general.yaml/.", destination: "/etc/OliveTin/config.yaml"
config.vm.provision "shell", inline: "mkdir /etc/OliveTin && chmod o+w /etc/OliveTin/ && mkdir -p /opt/OliveTin-configs/ && chmod 0777 /opt/OliveTin-configs", privileged: true
config.vm.provision "file", source: "tests/.", destination: "/tmp/test-configs/"
config.vm.provision "shell", inline: "for dir in /tmp/test-configs/*/; do if [ -f \"$dir/config.yaml\" ]; then cp -r \"$dir\" /opt/OliveTin-configs/$(basename \"$dir\")/; fi; done", privileged: true
config.vm.provider :libvirt do |libvirt|
libvirt.management_network_device = 'virbr0'
end
config.vm.define :f36 do |f36|
f36.vm.box = "generic/fedora36"
f36.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
f36.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
config.vm.define :stream9 do |i|
i.vm.box = "centos/stream9"
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
i.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
end
config.vm.define :debian do |debian|
debian.vm.box = "generic/debian10"
debian.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
debian.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin"
config.vm.define :fedora38 do |i|
i.vm.box = "generic/fedora38"
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
i.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
end
config.vm.define :ubuntu do |ubuntu|
ubuntu.vm.box = "generic/ubuntu2110"
ubuntu.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
ubuntu.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
config.vm.define :debian12 do |i|
i.vm.box = "debian/bookworm64"
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
i.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin"
end
# TODO
#
config.vm.define :ubuntu2310 do |i|
i.vm.box = "ubuntu/mantic64"
i.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
i.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
end
end

View File

@@ -0,0 +1,17 @@
---
version: "3.8"
services:
nginx:
container_name: nginx
image: docker.io/nginx
volumes:
- ./proxies/nginx/:/etc/nginx/
ports:
- "8443:8443"
restart: unless-stopped
networks:
- otproxy
networks:
otproxy:
name: otproxy

View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Run this like `. envVagrant.sh f38` before `mocha`
# args:
# $1: The Vagrant VM to test against. If blank and only one VM is provisioned, it will use that.
export IP=$(vagrant ssh-config $1 | grep HostName | awk '{print $2}')
export PORT=1337

View File

@@ -0,0 +1,143 @@
import { By } from 'selenium-webdriver'
import fs from 'fs'
import { expect } from 'chai'
import { Condition } from 'selenium-webdriver'
export async function getActionButtons (dashboardTitle = null) {
// New Vue UI renders action buttons using ActionButton.vue structure
// Each button lives under a container with class .action-button
if (dashboardTitle == null) {
return await webdriver.findElements(By.css('.action-button button'))
} else {
return await webdriver.findElements(By.css('section[title="' + dashboardTitle + '"] .action-button button'))
}
}
export async function getExecutionDialogOutput() {
await webdriver.wait(new Condition('Dialog with long int is visible', async () => {
const dialog = await webdriver.findElement({ id: 'execution-results-popup' })
return await dialog.isDisplayed()
}));
const ret = await webdriver.executeScript('return window.logEntries.get(window.executionDialog.executionTrackingId).output')
return ret
}
export async function closeExecutionDialog() {
const btnClose = await webdriver.findElements(By.css('[title="Close"]'))
await btnClose[0].click()
}
export function takeScreenshotOnFailure (test, webdriver) {
if (test.state === 'failed') {
const title = test.fullTitle();
console.log(`Test failed, taking screenshot: ${title}`);
takeScreenshot(webdriver, title);
}
}
export function takeScreenshot (webdriver, title) {
return webdriver.takeScreenshot().then((img) => {
fs.mkdirSync('screenshots', { recursive: true });
title = title.replaceAll('config: ', '')
title = title.replaceAll(/[\(\)\|\*\<\>\:]/g, "_")
title = title + '.failed-test'
fs.writeFileSync('screenshots/' + title + '.png', img, 'base64')
})
}
export async function getRootAndWait() {
await webdriver.get(runner.baseUrl())
await webdriver.wait(new Condition('wait for loaded-dashboard', async function() {
const body = await webdriver.findElement(By.tagName('body'))
const attr = await body.getAttribute('loaded-dashboard')
console.log('loaded-dashboard: ', attr)
if (attr) {
return true
} else {
return false
}
}))
}
export async function closeSidebar() {
await webdriver.findElement(By.id('sidebar-toggler-button')).click()
const sidebar = await webdriver.findElement(By.id('mainnav'))
const neededLeft = '-250px' // Assuming sidebar is closed at this position
let lastLeft = ''
await webdriver.wait(new Condition('wait for sidebar to close', async function() {
const left = await sidebar.getCssValue('left')
if (left !== lastLeft) {
lastLeft = left
console.log('Sidebar left changed to: ', left)
return false
} else {
console.log('Sidebar closed, left is: *' + left, left === neededLeft ? ' (as expected)' : '')
return left === neededLeft
}
}), 10000); // Wait up to 10 seconds for the sidebar to close
}
export async function openSidebar() {
await webdriver.findElement(By.id('sidebar-toggler-button')).click()
const sidebar = await webdriver.findElement(By.id('mainnav'))
let lastLeft = 0
await webdriver.wait(new Condition('wait for sidebar to open', async function() {
const left = await sidebar.getCssValue('left')
if (left !== lastLeft) {
lastLeft = left
console.log('Sidebar left changed to: ', left)
return false
} else {
console.log('Sidebar opened, left is: ', left)
return true
}
}));
}
export async function getNavigationLinks() {
const navigationLinks = await webdriver.findElements(By.css('.navigation-links li'))
return navigationLinks
}
export async function requireExecutionDialogStatus (webdriver, expected) {
await webdriver.wait(new Condition('wait for action to be running', async function () {
const dialogStatus = await webdriver.findElement(By.id('execution-dialog-status'))
const actual = await dialogStatus.getText()
if (actual === expected) {
return true
} else {
console.log('Waiting for domStatus text to be: ', expected, ', it is currently: ', actual)
return false
}
}))
}
export async function findExecutionDialog (webdriver) {
return webdriver.findElement(By.id('execution-results-popup'))
}
export async function getActionButton (webdriver, title) {
const buttons = await webdriver.findElements(By.css('[title="' + title + '"]'))
expect(buttons).to.have.length(1)
return buttons[0]
}

View File

@@ -10,7 +10,7 @@ export async function mochaGlobalSetup () {
global.runner = getRunner()
console.log("Runner constructor: " + global.runner.constructor.name)
console.log('Runner constructor: ' + global.runner.constructor.name)
}
export async function mochaGlobalTeardown () {

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,14 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"license": "AGPL-3.0-only",
"devDependencies": {
"chai": "^4.3.10",
"eslint": "^8.51.0",
"mocha": "^10.2.0",
"selenium-webdriver": "4.12.0"
"chai": "^6.2.1",
"eslint": "^9.39.1",
"mocha": "^11.7.5",
"selenium-webdriver": "^4.38.0"
},
"dependencies": {
"wait-on": "^7.0.1"
"wait-on": "^9.0.3"
}
}

View File

@@ -0,0 +1,3 @@
http://olivetin.example.com {
reverse_proxy * http://localhost:1337
}

View File

@@ -0,0 +1,2 @@
make:
haproxy -f ./haproxy.conf

View File

@@ -0,0 +1,32 @@
global
stats socket /var/run/haproxy/admin.sock mode 660 level admin
log stdout local0
frontend cleartext_frontend
bind 0.0.0.0:8080
timeout client 10s
stats enable
stats uri /stats
stats refresh 30s
log global
option httplog
option dontlognull
log-format "[%t] %ci:%cp -> BACKEND:%b/%s | %HM %HU | Status:%ST | Bytes:%B"
mode http
use_backend be_olivetin if { hdr(Host) -i -m beg olivetin.example.com }
backend be_olivetin
mode http
timeout connect 10s
timeout server 10s
timeout tunnel 1h
option http-server-close
http-request set-header X-Forwarded-User "Alice"
http-request set-header X-Forwarded-Group "Group1,Group2"
server olivetinServer 127.0.0.1:1337 check

View File

@@ -0,0 +1,9 @@
<VirtualHost *:80>
ServerName olivetin.example.com
ProxyPass / http://localhost:1337/
ProxyPassReverse / http://localhost:1337/
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/websocket
RewriteRule /(.) ws://localhost:1337/websocket [P,L]
</VirtualHost>

View File

@@ -0,0 +1,22 @@
server {
listen 8443 ssl;
ssl_certificate "/etc/nginx/conf.d/server.crt";
ssl_certificate_key "/etc/nginx/conf.d/server.key";
access_log /var/log/nginx/ot.access.log main;
error_log /var/log/nginx/ot.error.log notice;
server_name olivetin.example.com;
location / {
proxy_pass http://host.containers.internal:1337/;
proxy_redirect http://host.containers.internal:1337/ http://host.containers.internal/OliveTin/;
}
location /websocket {
proxy_set_header Upgrade "websocket";
proxy_set_header Connection "upgrade";
proxy_pass http://host.containers.internal:1337/websocket;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
# Settings for a TLS enabled server.
#
# server {
# listen 8443 ssl http2;
# listen [::]:8443 ssl http2;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers PROFILE=SYSTEM;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# error_page 404 /404.html;
# location = /404.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
}

View File

@@ -1,9 +1,7 @@
import process from 'node:process'
import * as process from 'node:process'
import waitOn from 'wait-on'
import { spawn } from 'node:child_process'
let ot = null
export default function getRunner () {
const type = process.env.OLIVETIN_TEST_RUNNER
@@ -11,51 +9,138 @@ export default function getRunner () {
switch (type) {
case 'local':
return new OliveTinTestRunnerLocalProcess()
return new OliveTinTestRunnerStartLocalProcess()
case 'vm':
return null
return new OliveTinTestRunnerVm()
case 'container':
return null
return new OliveTinTestRunnerEnv()
default:
return new OliveTinTestRunnerLocalProcess()
return new OliveTinTestRunnerStartLocalProcess()
}
}
class OliveTinTestRunnerLocalProcess {
class OliveTinTestRunner {
BASE_URL = 'http://nohost:1337/';
baseUrl() {
return this.BASE_URL
}
metricsUrl() {
return new URL('metrics', this.baseUrl());
}
}
class OliveTinTestRunnerStartLocalProcess extends OliveTinTestRunner {
async start (cfg) {
ot = spawn('./../OliveTin', ['-configdir', 'configs/' + cfg + '/'])
let stdout = ""
let stderr = ""
const logStdout = process.env.OLIVETIN_TEST_RUNNER_LOG_STDOUT === '1'
console.log(" OliveTin starting local process...")
if (logStdout) {
ot.stdout.on('data', (data) => {
console.log(`stdout: ${data}`)
})
this.ot = spawn('./../service/OliveTin', ['-configdir', 'tests/' + cfg + '/'])
ot.stderr.on('data', (data) => {
console.error(`stderr: ${data}`)
})
let logStdout = false
if (process.env.CI === 'true') {
logStdout = true;
} else {
logStdout = process.env.OLIVETIN_TEST_RUNNER_LOG_STDOUT === '1'
}
ot.on('close', (code) => {
if (code != null) {
console.log(`child process exited with code ${code}`)
this.ot.stdout.on('data', (data) => {
stdout += data
if (logStdout) {
console.log(`stdout: ${data}`)
}
})
/*
this.server = await startSomeServer({port: process.env.TEST_PORT});
console.log(`server running on port ${this.server.port}`);
*/
this.ot.stderr.on('data', (data) => {
stderr += data
await waitOn({
'resources': ['http://localhost:1337/']
if (logStdout) {
console.log(`stderr: ${data}`)
}
})
return ot
this.ot.on('close', (code) => {
if (code != null) {
console.log(`OliveTin local process exited with code ${code}`)
console.log(stdout)
console.log(stderr)
console.log(this.ot.exitCode)
}
})
if (this.ot.exitCode == null) {
this.BASE_URL = 'http://localhost:1337/'
console.log(" OliveTin waiting for local process to start...")
await waitOn({
resources: [this.BASE_URL]
})
console.log(" OliveTin local process started and webUI accessible")
} else {
console.log(" OliveTin local process start FAILED!")
console.log(stdout)
console.log(stderr)
console.log(this.ot.exitCode)
}
}
async stop () {
await ot.kill()
if ((await this.ot.exitCode) != null) {
console.log(" OliveTin local process tried stop(), but it already exited with code", this.ot.exitCode)
} else {
await this.ot.kill()
console.log(" OliveTin local process killed")
}
if (process.env.CI === 'true') {
// GitHub runners seem to need a bit more time to clean up
await new Promise((res) => setTimeout(res, 3000))
} else {
await new Promise((res) => setTimeout(res, 100))
}
}
}
class OliveTinTestRunnerEnv extends OliveTinTestRunner {
constructor () {
super()
const IP = process.env.IP
const PORT = process.env.PORT
this.BASE_URL = 'http://' + IP + ':' + PORT + '/'
console.log('Runner ENV endpoint: ' + this.BASE_URL)
}
async start () {
await waitOn({
resources: [this.BASE_URL]
})
}
async stop () {
}
}
class OliveTinTestRunnerVm extends OliveTinTestRunnerEnv {
constructor() {
super()
}
async start (cfg) {
console.log("vagrant changing config")
spawn('vagrant', ['ssh', '-c', '"ln -sf /etc/OliveTin/ /opt/OliveTin-configs/' + cfg + '/config.yaml"'])
spawn('vagrant', ['ssh', '-c', '"systemctl restart OliveTin"'])
return null
}
}

View File

@@ -1,35 +0,0 @@
import {expect} from 'chai';
import {By} from 'selenium-webdriver';
describe('config: general', function () {
before(async function () {
await runner.start('general')
});
after(async () => {
await runner.stop()
});
it('Page title', async function () {
await webdriver.get('http://localhost:1337')
let title = await webdriver.getTitle();
expect(title).to.be.equal("OliveTin")
})
it('Footer contains promo', async function () {
let ftr = await webdriver.findElement(By.tagName('footer')).getText()
expect(ftr).to.contain("Documentation")
})
it('Default buttons are rendered', async function() {
await webdriver.get('http://localhost:1337')
// await webdriver.manage().setTimeouts({ implicit: 2000 });
let buttons = await webdriver.findElement(By.id('root-group')).findElements(By.tagName('button'))
expect(buttons).to.have.length(6);
})
})

View File

@@ -1,21 +0,0 @@
import { expect } from 'chai';
import { By } from 'selenium-webdriver';
describe('config: hiddenFooter', function () {
before(async function () {
await runner.start('hiddenFooter')
});
after(async () => {
await runner.stop()
});
it('Check that footer is hidden', async () => {
await webdriver.get('http://localhost:1337')
let footer = await webdriver.findElement(By.tagName('footer'))
expect(await footer.isDisplayed()).to.be.false
})
})

View File

@@ -1,20 +0,0 @@
import {expect} from 'chai';
import {By} from 'selenium-webdriver';
describe('config: hiddenNav', function () {
before(async function () {
await runner.start('hiddenNav')
})
after(async () => {
await runner.stop()
})
it('nav is hidden', async () => {
await webdriver.get('http://localhost:1337')
const toggler = await webdriver.findElement(By.id('sidebar-toggle-wrapper'))
expect(await toggler.isDisplayed()).to.be.false
})
})

View File

@@ -1,34 +0,0 @@
import { expect } from 'chai'
import { By, until } from 'selenium-webdriver'
import fs from 'node:fs'
describe('config: multipleDropdowns', function () {
before(async function () {
await runner.start('multipleDropdowns')
})
after(async () => {
await runner.stop()
})
it('Multiple dropdowns are possible', async function() {
await webdriver.get('http://localhost:1337')
await webdriver.manage().setTimeouts({ implicit: 2000 });
const button = await webdriver.findElement(By.id('actionButton_bdc45101bbd12c1397557790d9f3e059')).findElement(By.tagName('button'));
expect(button).to.not.be.undefined;
await button.click()
const dialog = await webdriver.findElement(By.id('argument-popup'));
await webdriver.wait(until.elementIsVisible(dialog), 2000)
const selects = await dialog.findElements(By.tagName('select'))
expect(selects).to.have.length(2)
expect(await selects[0].findElements(By.tagName('option'))).to.have.length(2)
expect(await selects[1].findElements(By.tagName('option'))).to.have.length(3)
})
})

View File

@@ -0,0 +1,39 @@
import { describe, it, before, after } from 'mocha'
import { expect } from 'chai'
import { By, until } from 'selenium-webdriver'
import {
getRootAndWait,
takeScreenshotOnFailure,
} from '../../lib/elements.js'
describe('config: authRequireGuestsToLogin', function () {
this.timeout(30000)
before(async function () {
await runner.start('authRequireGuestsToLogin')
})
after(async () => {
await runner.stop()
})
afterEach(function () {
takeScreenshotOnFailure(this.currentTest, webdriver);
});
it('Guest is redirected to login', async function () {
// Don't use getRootAndWait here because we want to test the redirect, and getRootAndWait waits for the dashboard to load
await webdriver.get(runner.baseUrl())
await webdriver.wait(until.urlContains('/login'), 10000)
// Verify login UI elements are present
const loginElements = await webdriver.findElements(By.css('form.local-login-form, .login-oauth2, .login-disabled'))
expect(loginElements.length).to.be.greaterThan(0)
console.log('✓ Login page loaded correctly')
})
})

Some files were not shown because too many files have changed in this diff Show More