diff --git a/config.yaml b/config.yaml index 37f555c..9ee2c2d 100644 --- a/config.yaml +++ b/config.yaml @@ -2,7 +2,8 @@ # one port (this is called the "Single HTTP Frontend") and means you just need # one open port in the container/firewalls/etc. # -# Listen on all addresses available, port 1337 +# Listen on all addresses available, port 1337. +# If the PORT environment variable is set, it overrides this port (host is kept). listenAddressSingleHTTPFrontend: 0.0.0.0:1337 # Choose from INFO (default), WARN and DEBUG @@ -352,8 +353,10 @@ actionGroups: # # Docs: https://docs.olivetin.app/dashboards/intro.html dashboards: - # Top level items are dashboards. + # Top level items are dashboards. Optional `category` groups them under + # collapsible headings in the sidebar (same label = same group). - title: My Servers + category: Infrastructure contents: - title: All Servers type: fieldset @@ -399,8 +402,10 @@ dashboards: contents: - title: '{{ server.name }} Print server name' - # This is the second dashboard. + # Second dashboard — same category as My Servers, so both appear under + # "Infrastructure" in the sidebar. - title: My Containers + category: Infrastructure contents: - title: 'Container {{ .CurrentEntity.Names }} ({{ .CurrentEntity.Image }})' entity: container diff --git a/docs/modules/ROOT/pages/advanced_configuration/webui.adoc b/docs/modules/ROOT/pages/advanced_configuration/webui.adoc index 504d349..a6d1620 100644 --- a/docs/modules/ROOT/pages/advanced_configuration/webui.adoc +++ b/docs/modules/ROOT/pages/advanced_configuration/webui.adoc @@ -67,6 +67,8 @@ showNavigateOnStartIcons: false image::advanced_configuration/webui/sidebar/sidebar.png[] +Root dashboards can optionally set a `category` field so the sidebar groups them under collapsible headings. See xref:dashboards/intro.adoc#categorize-dashboards-in-the-sidebar[Categorize dashboards in the sidebar]. + === Topbar navigation style `sectionNavigationStyle: topbar` looks like this; diff --git a/docs/modules/ROOT/pages/dashboards/intro.adoc b/docs/modules/ROOT/pages/dashboards/intro.adoc index 0aca815..5f30365 100644 --- a/docs/modules/ROOT/pages/dashboards/intro.adoc +++ b/docs/modules/ROOT/pages/dashboards/intro.adoc @@ -82,6 +82,39 @@ dashboards: - title: '{{ server.name }} Power Off' ---- +[#categorize-dashboards-in-the-sidebar] +== Categorize dashboards in the sidebar + +When you have several root dashboards, you can group them under collapsible category headings in the sidebar with an optional `category` field on each root dashboard: + +[source,yaml] +.`config.yaml` +---- +dashboards: + - title: My Servers + category: Infrastructure + contents: + - title: Ping All Servers + + - title: My Containers + category: Infrastructure + contents: + - title: Restart nginx + + - title: Status Board + category: Monitoring + contents: + - title: Show uptime + + - title: Misc Tools + contents: + - title: Hello World +---- + +Dashboards that share the same `category` value are grouped together. Category order follows the first time each category appears in the config among dashboards the user can see. Dashboards without a `category` appear above the category sections. Nested items under `contents` do not use `category`. + +Categories are shown when `sectionNavigationStyle` is `sidebar` (the default). See xref:advanced_configuration/webui.adoc#section-navgiation-style[Section Navigation Style]. + == What's Next? Now that you understand dashboards, explore these related features: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 58f11e9..6894831 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,7 +18,7 @@ "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", "iconify-icon": "^3.0.2", - "picocrank": "^1.21.1", + "picocrank": "^1.21.2", "standard": "^17.1.2", "unplugin-vue-components": "^32.1.0", "vite": "^8.1.5", @@ -5272,9 +5272,9 @@ "license": "ISC" }, "node_modules/picocrank": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.21.1.tgz", - "integrity": "sha512-VnfNFok7BkpLKymAIbD2zUA0bf/AiNBS63Xk2mfi8uXOkdAmf06pQaI0ssGBv2/S9p1jbCATWj9OFz1uqa39NQ==", + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.21.2.tgz", + "integrity": "sha512-bUw6789jyYCTa9waINvYRHmnfgtoMquUidkJx2yhrDCMGbu2O65Qilkl++57yl5JnFeoZOhiPuQTKMPxMQku4A==", "license": "ISC", "dependencies": { "@hugeicons/core-free-icons": "^4.2.2", diff --git a/frontend/package.json b/frontend/package.json index 5762813..018aa73 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,7 @@ "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", "iconify-icon": "^3.0.2", - "picocrank": "^1.21.1", + "picocrank": "^1.21.2", "standard": "^17.1.2", "unplugin-vue-components": "^32.1.0", "vite": "^8.1.5", diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts index 0f482a3..acc3f89 100644 --- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts +++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts @@ -1890,6 +1890,11 @@ export declare type InitResponse = Message<"olivetin.api.v1.InitResponse"> & { * @generated from field: int32 config_issue_count = 26; */ configIssueCount: number; + + /** + * @generated from field: repeated olivetin.api.v1.RootDashboard root_dashboard_entries = 27; + */ + rootDashboardEntries: RootDashboard[]; }; /** @@ -1898,6 +1903,27 @@ export declare type InitResponse = Message<"olivetin.api.v1.InitResponse"> & { */ export declare const InitResponseSchema: GenMessage; +/** + * @generated from message olivetin.api.v1.RootDashboard + */ +export declare type RootDashboard = Message<"olivetin.api.v1.RootDashboard"> & { + /** + * @generated from field: string title = 1; + */ + title: string; + + /** + * @generated from field: string category = 2; + */ + category: string; +}; + +/** + * Describes the message olivetin.api.v1.RootDashboard. + * Use `create(RootDashboardSchema)` to create a new message. + */ +export declare const RootDashboardSchema: GenMessage; + /** * @generated from message olivetin.api.v1.AdditionalLink */ diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js index df4fcd9..5f363b2 100644 --- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js +++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js @@ -8,7 +8,7 @@ import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2 * Describes the file olivetin/api/v1/olivetin.proto. */ export const file_olivetin_api_v1_olivetin = /*@__PURE__*/ - fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLGBAoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkSFwoPZXhlY19vbl9zdGFydHVwGAogASgIEhQKDGV4ZWNfb25fY3JvbhgLIAMoCRIjChtleGVjX29uX2ZpbGVfY3JlYXRlZF9pbl9kaXIYDCADKAkSIwobZXhlY19vbl9maWxlX2NoYW5nZWRfaW5fZGlyGA0gAygJEh0KFWV4ZWNfb25fY2FsZW5kYXJfZmlsZRgOIAEoCRJAChBleGVjX29uX3dlYmhvb2tzGA8gAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludBIVCg1qdXN0aWZpY2F0aW9uGBQgASgJEhwKFGhhc19ydW5uaW5nX2luc3RhbmNlGBEgASgIEhsKE2hhc19xdWV1ZWRfaW5zdGFuY2UYEiABKAgSNgoGZ3JvdXBzGBMgAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkdyb3VwTWVtYmVyc2hpcEoECBAQESJRChVBY3Rpb25Hcm91cE1lbWJlcnNoaXASDAoEbmFtZRgBIAEoCRIWCg5tYXhfY29uY3VycmVudBgCIAEoBRISCgpxdWV1ZV9zaXplGAMgASgFIsMCChVBY3Rpb25XZWJob29rRXhlY0hpbnQSEAoIdGVtcGxhdGUYASABKAkSEgoKbWF0Y2hfcGF0aBgCIAEoCRJPCg1tYXRjaF9oZWFkZXJzGAMgAygLMjgub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludC5NYXRjaEhlYWRlcnNFbnRyeRJLCgttYXRjaF9xdWVyeRgEIAMoCzI2Lm9saXZldGluLmFwaS52MS5BY3Rpb25XZWJob29rRXhlY0hpbnQuTWF0Y2hRdWVyeUVudHJ5GjMKEU1hdGNoSGVhZGVyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaMQoPTWF0Y2hRdWVyeUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAki1AEKE0VudGl0eVJlbGF0ZWRBY3Rpb24SJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbhJZChNwcmVmaWxsZWRfYXJndW1lbnRzGAIgAygLMjwub2xpdmV0aW4uYXBpLnYxLkVudGl0eVJlbGF0ZWRBY3Rpb24uUHJlZmlsbGVkQXJndW1lbnRzRW50cnkaOQoXUHJlZmlsbGVkQXJndW1lbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASL/AQoGRW50aXR5Eg0KBXRpdGxlGAEgASgJEhIKCnVuaXF1ZV9rZXkYAiABKAkSDAoEdHlwZRgDIAEoCRITCgtkaXJlY3RvcmllcxgEIAMoCRIzCgZmaWVsZHMYBSADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5LkZpZWxkc0VudHJ5Ej0KD3JlbGF0ZWRfYWN0aW9ucxgGIAMoCzIkLm9saXZldGluLmFwaS52MS5FbnRpdHlSZWxhdGVkQWN0aW9uEgwKBGljb24YByABKAkaLQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJUChRHZXREYXNoYm9hcmRSZXNwb25zZRINCgV0aXRsZRgBIAEoCRItCglkYXNoYm9hcmQYBCABKAsyGi5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkIl8KD0VmZmVjdGl2ZVBvbGljeRIYChBzaG93X2RpYWdub3N0aWNzGAEgASgIEhUKDXNob3dfbG9nX2xpc3QYAiABKAgSGwoTc2hvd192ZXJzaW9uX251bWJlchgDIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSKUAQoSU3RhcnRBY3Rpb25SZXF1ZXN0EhIKCmJpbmRpbmdfaWQYASABKAkSNwoJYXJndW1lbnRzGAIgAygLMiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQXJndW1lbnQSGgoSdW5pcXVlX3RyYWNraW5nX2lkGAMgASgJEhUKDWp1c3RpZmljYXRpb24YBCABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIn4KGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50EhUKDWp1c3RpZmljYXRpb24YAyABKAkiSgoaU3RhcnRBY3Rpb25BbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IiwKF1N0YXJ0QWN0aW9uQnlHZXRSZXF1ZXN0EhEKCWFjdGlvbl9pZBgBIAEoCSI5ChhTdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIjMKHlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiTwofU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZRIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiXgoOR2V0TG9nc1JlcXVlc3QSFAoMc3RhcnRfb2Zmc2V0GAEgASgDEhMKC2RhdGVfZmlsdGVyGAIgASgJEhEKCXBhZ2Vfc2l6ZRgDIAEoAxIOCgZmaWx0ZXIYBCABKAkilAQKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCRIOCgZxdWV1ZWQYFSABKAgSGAoQcXVldWVkX2Zvcl9ncm91cBgWIAEoCRIVCg1qdXN0aWZpY2F0aW9uGBcgASgJEjcKCWFyZ3VtZW50cxgYIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IpEBCg9HZXRMb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI/ChRHZXRBY3Rpb25Mb2dzUmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSFAoMc3RhcnRfb2Zmc2V0GAIgASgDIpcBChVHZXRBY3Rpb25Mb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyIaChhHZXRFeGVjdXRpb25RdWV1ZVJlcXVlc3QixgEKFEV4ZWN1dGlvblF1ZXVlQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEhMKC2FjdGlvbl9pY29uGAMgASgJEhYKDm1heF9jb25jdXJyZW50GAQgASgFEhQKDGFjdGl2ZV9jb3VudBgFIAEoBRIVCg1lbnRpdHlfcHJlZml4GAYgASgJEioKB2VudHJpZXMYByADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiwQEKE0V4ZWN1dGlvblF1ZXVlR3JvdXASDAoEbmFtZRgBIAEoCRIMCgRpY29uGAIgASgJEhYKDm1heF9jb25jdXJyZW50GAMgASgFEhQKDGFjdGl2ZV9jb3VudBgEIAEoBRI2CgdhY3Rpb25zGAUgAygLMiUub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblF1ZXVlQWN0aW9uEhQKDHF1ZXVlZF9jb3VudBgGIAEoBRISCgpxdWV1ZV9zaXplGAcgASgFImcKGUdldEV4ZWN1dGlvblF1ZXVlUmVzcG9uc2USNAoGZ3JvdXBzGAEgAygLMiQub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblF1ZXVlR3JvdXASFAoMdG90YWxfYWN0aXZlGAIgASgFImUKG1ZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBINCgV2YWx1ZRgBIAEoCRIMCgR0eXBlGAIgASgJEhIKCmJpbmRpbmdfaWQYAyABKAkSFQoNYXJndW1lbnRfbmFtZRgEIAEoCSJCChxWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlEg0KBXZhbGlkGAEgASgIEhMKC2Rlc2NyaXB0aW9uGAIgASgJIjYKFVdhdGNoRXhlY3V0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkiJgoUV2F0Y2hFeGVjdXRpb25VcGRhdGUSDgoGdXBkYXRlGAEgASgJIkoKFkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEhEKCWFjdGlvbl9pZBgCIAEoCSJhChlEYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkSDAoEcGF0aBgEIAEoCSKPAQoXRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5EkYKEmJhY2tfdG9fZGFzaGJvYXJkcxgCIAMoCzIqLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0Ig8KDVdob0FtSVJlcXVlc3QibAoOV2hvQW1JUmVzcG9uc2USGgoSYXV0aGVudGljYXRlZF91c2VyGAEgASgJEhEKCXVzZXJncm91cBgCIAEoCRIQCghwcm92aWRlchgDIAEoCRIMCgRhY2xzGAQgAygJEgsKA3NpZBgFIAEoCSIaChhTZXJ2ZXJEaWFnbm9zdGljc1JlcXVlc3QiKgoZU2VydmVyRGlhZ25vc3RpY3NSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCKZAwoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSAASNAoJaGVhcnRiZWF0GAcgASgLMh8ub2xpdmV0aW4uYXBpLnYxLkV2ZW50SGVhcnRiZWF0SABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiEAoORXZlbnRIZWFydGJlYXQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiowEKC0NvbmZpZ0lzc3VlEhAKCHNldmVyaXR5GAEgASgJEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIRCglhY3Rpb25faWQYBCABKAkSFAoMYWN0aW9uX3RpdGxlGAUgASgJEhUKDWFyZ3VtZW50X25hbWUYBiABKAkSDgoGc291cmNlGAcgASgJEhMKC2NvbmZpZ19maWxlGAggASgJInoKFkdldERpYWdub3N0aWNzUmVzcG9uc2USEwoLU3NoRm91bmRLZXkYASABKAkSFgoOU3NoRm91bmRDb25maWcYAiABKAkSMwoNY29uZmlnX2lzc3VlcxgDIAMoCzIcLm9saXZldGluLmFwaS52MS5Db25maWdJc3N1ZSINCgtJbml0UmVxdWVzdCKHBgoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCBIaChJjb25maWdfaXNzdWVfY291bnQYGiABKAUiLAoOQWRkaXRpb25hbExpbmsSDQoFdGl0bGUYASABKAkSCwoDdXJsGAIgASgJIjoKDk9BdXRoMlByb3ZpZGVyEg0KBXRpdGxlGAEgASgJEgwKBGljb24YAyABKAkSCwoDa2V5GAQgASgJIi0KF0dldEFjdGlvbkJpbmRpbmdSZXF1ZXN0EhIKCmJpbmRpbmdfaWQYASABKAkiiwEKGEdldEFjdGlvbkJpbmRpbmdSZXNwb25zZRInCgZhY3Rpb24YASABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uEkYKEmJhY2tfdG9fZGFzaGJvYXJkcxgCIAMoCzIqLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0IloKEkdldEVudGl0aWVzUmVxdWVzdBITCgtlbnRpdHlfdHlwZRgBIAEoCRIOCgZmaWx0ZXIYAiABKAkSDAoEcGFnZRgDIAEoBRIRCglwYWdlX3NpemUYBCABKAUiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiLFAQoQRW50aXR5RGVmaW5pdGlvbhINCgV0aXRsZRgBIAEoCRIqCglpbnN0YW5jZXMYAiADKAsyFy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5EhoKEnVzZWRfb25fZGFzaGJvYXJkcxgDIAMoCRIMCgRpY29uGAQgASgJEjMKCnByb3BlcnRpZXMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5UHJvcGVydHkSFwoPdG90YWxfaW5zdGFuY2VzGAYgASgFIi0KDkVudGl0eVByb3BlcnR5EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkiNAoQR2V0RW50aXR5UmVxdWVzdBISCgp1bmlxdWVfa2V5GAEgASgJEgwKBHR5cGUYAiABKAkiNQoUUmVzdGFydEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJMu4TChJPbGl2ZVRpbkFwaVNlcnZpY2USXQoMR2V0RGFzaGJvYXJkEiQub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuR2V0RGFzaGJvYXJkUmVzcG9uc2UiABJaCgtTdGFydEFjdGlvbhIjLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAEm8KElN0YXJ0QWN0aW9uQW5kV2FpdBIqLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXF1ZXN0Gisub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlIgASaQoQU3RhcnRBY3Rpb25CeUdldBIoLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBopLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2UiABJ+ChdTdGFydEFjdGlvbkJ5R2V0QW5kV2FpdBIvLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QaMC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZSIAEl4KDVJlc3RhcnRBY3Rpb24SJS5vbGl2ZXRpbi5hcGkudjEuUmVzdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAElcKCktpbGxBY3Rpb24SIi5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlcXVlc3QaIy5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlc3BvbnNlIgASZgoPRXhlY3V0aW9uU3RhdHVzEicub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QaKC5vbGl2ZXRpbi5hcGkudjEuRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2UiABJOCgdHZXRMb2dzEh8ub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXF1ZXN0GiAub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXNwb25zZSIAEmAKDUdldEFjdGlvbkxvZ3MSJS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1JlcXVlc3QaJi5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1Jlc3BvbnNlIgASbAoRR2V0RXhlY3V0aW9uUXVldWUSKS5vbGl2ZXRpbi5hcGkudjEuR2V0RXhlY3V0aW9uUXVldWVSZXF1ZXN0Gioub2xpdmV0aW4uYXBpLnYxLkdldEV4ZWN1dGlvblF1ZXVlUmVzcG9uc2UiABJ1ChRWYWxpZGF0ZUFyZ3VtZW50VHlwZRIsLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlcXVlc3QaLS5vbGl2ZXRpbi5hcGkudjEuVmFsaWRhdGVBcmd1bWVudFR5cGVSZXNwb25zZSIAEksKBldob0FtSRIeLm9saXZldGluLmFwaS52MS5XaG9BbUlSZXF1ZXN0Gh8ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlc3BvbnNlIgASbAoRU2VydmVyRGlhZ25vc3RpY3MSKS5vbGl2ZXRpbi5hcGkudjEuU2VydmVyRGlhZ25vc3RpY3NSZXF1ZXN0Gioub2xpdmV0aW4uYXBpLnYxLlNlcnZlckRpYWdub3N0aWNzUmVzcG9uc2UiABJRCghEdW1wVmFycxIgLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1JlcXVlc3QaIS5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZSIAEngKFUR1bXBQdWJsaWNJZEFjdGlvbk1hcBItLm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXF1ZXN0Gi4ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlc3BvbnNlIgASVAoJR2V0UmVhZHl6EiEub2xpdmV0aW4uYXBpLnYxLkdldFJlYWR5elJlcXVlc3QaIi5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVzcG9uc2UiABJjCg5Mb2NhbFVzZXJMb2dpbhImLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuTG9jYWxVc2VyTG9naW5SZXNwb25zZSIAEl0KDFBhc3N3b3JkSGFzaBIkLm9saXZldGluLmFwaS52MS5QYXNzd29yZEhhc2hSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlc3BvbnNlIgASSwoGTG9nb3V0Eh4ub2xpdmV0aW4uYXBpLnYxLkxvZ291dFJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVzcG9uc2UiABJcCgtFdmVudFN0cmVhbRIjLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuRXZlbnRTdHJlYW1SZXNwb25zZSIAMAESYwoOR2V0RGlhZ25vc3RpY3MSJi5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXF1ZXN0Gicub2xpdmV0aW4uYXBpLnYxLkdldERpYWdub3N0aWNzUmVzcG9uc2UiABJFCgRJbml0Ehwub2xpdmV0aW4uYXBpLnYxLkluaXRSZXF1ZXN0Gh0ub2xpdmV0aW4uYXBpLnYxLkluaXRSZXNwb25zZSIAEmkKEEdldEFjdGlvbkJpbmRpbmcSKC5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uQmluZGluZ1Jlc3BvbnNlIgASWgoLR2V0RW50aXRpZXMSIy5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0aWVzUmVzcG9uc2UiABJJCglHZXRFbnRpdHkSIS5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXR5UmVxdWVzdBoXLm9saXZldGluLmFwaS52MS5FbnRpdHkiAEI4WjZnaXRodWIuY29tL09saXZlVGluL09saXZlVGluL2dlbi9vbGl2ZXRpbi9hcGkvdjE7YXBpdjFiBnByb3RvMw"); + fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLGBAoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkSFwoPZXhlY19vbl9zdGFydHVwGAogASgIEhQKDGV4ZWNfb25fY3JvbhgLIAMoCRIjChtleGVjX29uX2ZpbGVfY3JlYXRlZF9pbl9kaXIYDCADKAkSIwobZXhlY19vbl9maWxlX2NoYW5nZWRfaW5fZGlyGA0gAygJEh0KFWV4ZWNfb25fY2FsZW5kYXJfZmlsZRgOIAEoCRJAChBleGVjX29uX3dlYmhvb2tzGA8gAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludBIVCg1qdXN0aWZpY2F0aW9uGBQgASgJEhwKFGhhc19ydW5uaW5nX2luc3RhbmNlGBEgASgIEhsKE2hhc19xdWV1ZWRfaW5zdGFuY2UYEiABKAgSNgoGZ3JvdXBzGBMgAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkdyb3VwTWVtYmVyc2hpcEoECBAQESJRChVBY3Rpb25Hcm91cE1lbWJlcnNoaXASDAoEbmFtZRgBIAEoCRIWCg5tYXhfY29uY3VycmVudBgCIAEoBRISCgpxdWV1ZV9zaXplGAMgASgFIsMCChVBY3Rpb25XZWJob29rRXhlY0hpbnQSEAoIdGVtcGxhdGUYASABKAkSEgoKbWF0Y2hfcGF0aBgCIAEoCRJPCg1tYXRjaF9oZWFkZXJzGAMgAygLMjgub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludC5NYXRjaEhlYWRlcnNFbnRyeRJLCgttYXRjaF9xdWVyeRgEIAMoCzI2Lm9saXZldGluLmFwaS52MS5BY3Rpb25XZWJob29rRXhlY0hpbnQuTWF0Y2hRdWVyeUVudHJ5GjMKEU1hdGNoSGVhZGVyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaMQoPTWF0Y2hRdWVyeUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAki1AEKE0VudGl0eVJlbGF0ZWRBY3Rpb24SJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbhJZChNwcmVmaWxsZWRfYXJndW1lbnRzGAIgAygLMjwub2xpdmV0aW4uYXBpLnYxLkVudGl0eVJlbGF0ZWRBY3Rpb24uUHJlZmlsbGVkQXJndW1lbnRzRW50cnkaOQoXUHJlZmlsbGVkQXJndW1lbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASL/AQoGRW50aXR5Eg0KBXRpdGxlGAEgASgJEhIKCnVuaXF1ZV9rZXkYAiABKAkSDAoEdHlwZRgDIAEoCRITCgtkaXJlY3RvcmllcxgEIAMoCRIzCgZmaWVsZHMYBSADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5LkZpZWxkc0VudHJ5Ej0KD3JlbGF0ZWRfYWN0aW9ucxgGIAMoCzIkLm9saXZldGluLmFwaS52MS5FbnRpdHlSZWxhdGVkQWN0aW9uEgwKBGljb24YByABKAkaLQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJUChRHZXREYXNoYm9hcmRSZXNwb25zZRINCgV0aXRsZRgBIAEoCRItCglkYXNoYm9hcmQYBCABKAsyGi5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkIl8KD0VmZmVjdGl2ZVBvbGljeRIYChBzaG93X2RpYWdub3N0aWNzGAEgASgIEhUKDXNob3dfbG9nX2xpc3QYAiABKAgSGwoTc2hvd192ZXJzaW9uX251bWJlchgDIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSKUAQoSU3RhcnRBY3Rpb25SZXF1ZXN0EhIKCmJpbmRpbmdfaWQYASABKAkSNwoJYXJndW1lbnRzGAIgAygLMiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQXJndW1lbnQSGgoSdW5pcXVlX3RyYWNraW5nX2lkGAMgASgJEhUKDWp1c3RpZmljYXRpb24YBCABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIn4KGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50EhUKDWp1c3RpZmljYXRpb24YAyABKAkiSgoaU3RhcnRBY3Rpb25BbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IiwKF1N0YXJ0QWN0aW9uQnlHZXRSZXF1ZXN0EhEKCWFjdGlvbl9pZBgBIAEoCSI5ChhTdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIjMKHlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiTwofU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZRIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiXgoOR2V0TG9nc1JlcXVlc3QSFAoMc3RhcnRfb2Zmc2V0GAEgASgDEhMKC2RhdGVfZmlsdGVyGAIgASgJEhEKCXBhZ2Vfc2l6ZRgDIAEoAxIOCgZmaWx0ZXIYBCABKAkilAQKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCRIOCgZxdWV1ZWQYFSABKAgSGAoQcXVldWVkX2Zvcl9ncm91cBgWIAEoCRIVCg1qdXN0aWZpY2F0aW9uGBcgASgJEjcKCWFyZ3VtZW50cxgYIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IpEBCg9HZXRMb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI/ChRHZXRBY3Rpb25Mb2dzUmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSFAoMc3RhcnRfb2Zmc2V0GAIgASgDIpcBChVHZXRBY3Rpb25Mb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyIaChhHZXRFeGVjdXRpb25RdWV1ZVJlcXVlc3QixgEKFEV4ZWN1dGlvblF1ZXVlQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEhMKC2FjdGlvbl9pY29uGAMgASgJEhYKDm1heF9jb25jdXJyZW50GAQgASgFEhQKDGFjdGl2ZV9jb3VudBgFIAEoBRIVCg1lbnRpdHlfcHJlZml4GAYgASgJEioKB2VudHJpZXMYByADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiwQEKE0V4ZWN1dGlvblF1ZXVlR3JvdXASDAoEbmFtZRgBIAEoCRIMCgRpY29uGAIgASgJEhYKDm1heF9jb25jdXJyZW50GAMgASgFEhQKDGFjdGl2ZV9jb3VudBgEIAEoBRI2CgdhY3Rpb25zGAUgAygLMiUub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblF1ZXVlQWN0aW9uEhQKDHF1ZXVlZF9jb3VudBgGIAEoBRISCgpxdWV1ZV9zaXplGAcgASgFImcKGUdldEV4ZWN1dGlvblF1ZXVlUmVzcG9uc2USNAoGZ3JvdXBzGAEgAygLMiQub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblF1ZXVlR3JvdXASFAoMdG90YWxfYWN0aXZlGAIgASgFImUKG1ZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBINCgV2YWx1ZRgBIAEoCRIMCgR0eXBlGAIgASgJEhIKCmJpbmRpbmdfaWQYAyABKAkSFQoNYXJndW1lbnRfbmFtZRgEIAEoCSJCChxWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlEg0KBXZhbGlkGAEgASgIEhMKC2Rlc2NyaXB0aW9uGAIgASgJIjYKFVdhdGNoRXhlY3V0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkiJgoUV2F0Y2hFeGVjdXRpb25VcGRhdGUSDgoGdXBkYXRlGAEgASgJIkoKFkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEhEKCWFjdGlvbl9pZBgCIAEoCSJhChlEYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkSDAoEcGF0aBgEIAEoCSKPAQoXRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5EkYKEmJhY2tfdG9fZGFzaGJvYXJkcxgCIAMoCzIqLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0Ig8KDVdob0FtSVJlcXVlc3QibAoOV2hvQW1JUmVzcG9uc2USGgoSYXV0aGVudGljYXRlZF91c2VyGAEgASgJEhEKCXVzZXJncm91cBgCIAEoCRIQCghwcm92aWRlchgDIAEoCRIMCgRhY2xzGAQgAygJEgsKA3NpZBgFIAEoCSIaChhTZXJ2ZXJEaWFnbm9zdGljc1JlcXVlc3QiKgoZU2VydmVyRGlhZ25vc3RpY3NSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCKZAwoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSAASNAoJaGVhcnRiZWF0GAcgASgLMh8ub2xpdmV0aW4uYXBpLnYxLkV2ZW50SGVhcnRiZWF0SABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiEAoORXZlbnRIZWFydGJlYXQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiowEKC0NvbmZpZ0lzc3VlEhAKCHNldmVyaXR5GAEgASgJEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIRCglhY3Rpb25faWQYBCABKAkSFAoMYWN0aW9uX3RpdGxlGAUgASgJEhUKDWFyZ3VtZW50X25hbWUYBiABKAkSDgoGc291cmNlGAcgASgJEhMKC2NvbmZpZ19maWxlGAggASgJInoKFkdldERpYWdub3N0aWNzUmVzcG9uc2USEwoLU3NoRm91bmRLZXkYASABKAkSFgoOU3NoRm91bmRDb25maWcYAiABKAkSMwoNY29uZmlnX2lzc3VlcxgDIAMoCzIcLm9saXZldGluLmFwaS52MS5Db25maWdJc3N1ZSINCgtJbml0UmVxdWVzdCLHBgoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCBIaChJjb25maWdfaXNzdWVfY291bnQYGiABKAUSPgoWcm9vdF9kYXNoYm9hcmRfZW50cmllcxgbIAMoCzIeLm9saXZldGluLmFwaS52MS5Sb290RGFzaGJvYXJkIjAKDVJvb3REYXNoYm9hcmQSDQoFdGl0bGUYASABKAkSEAoIY2F0ZWdvcnkYAiABKAkiLAoOQWRkaXRpb25hbExpbmsSDQoFdGl0bGUYASABKAkSCwoDdXJsGAIgASgJIjoKDk9BdXRoMlByb3ZpZGVyEg0KBXRpdGxlGAEgASgJEgwKBGljb24YAyABKAkSCwoDa2V5GAQgASgJIi0KF0dldEFjdGlvbkJpbmRpbmdSZXF1ZXN0EhIKCmJpbmRpbmdfaWQYASABKAkiiwEKGEdldEFjdGlvbkJpbmRpbmdSZXNwb25zZRInCgZhY3Rpb24YASABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uEkYKEmJhY2tfdG9fZGFzaGJvYXJkcxgCIAMoCzIqLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmROYXZpZ2F0aW9uVGFyZ2V0IloKEkdldEVudGl0aWVzUmVxdWVzdBITCgtlbnRpdHlfdHlwZRgBIAEoCRIOCgZmaWx0ZXIYAiABKAkSDAoEcGFnZRgDIAEoBRIRCglwYWdlX3NpemUYBCABKAUiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiLFAQoQRW50aXR5RGVmaW5pdGlvbhINCgV0aXRsZRgBIAEoCRIqCglpbnN0YW5jZXMYAiADKAsyFy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5EhoKEnVzZWRfb25fZGFzaGJvYXJkcxgDIAMoCRIMCgRpY29uGAQgASgJEjMKCnByb3BlcnRpZXMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5UHJvcGVydHkSFwoPdG90YWxfaW5zdGFuY2VzGAYgASgFIi0KDkVudGl0eVByb3BlcnR5EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkiNAoQR2V0RW50aXR5UmVxdWVzdBISCgp1bmlxdWVfa2V5GAEgASgJEgwKBHR5cGUYAiABKAkiNQoUUmVzdGFydEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJMu4TChJPbGl2ZVRpbkFwaVNlcnZpY2USXQoMR2V0RGFzaGJvYXJkEiQub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuR2V0RGFzaGJvYXJkUmVzcG9uc2UiABJaCgtTdGFydEFjdGlvbhIjLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAEm8KElN0YXJ0QWN0aW9uQW5kV2FpdBIqLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXF1ZXN0Gisub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlIgASaQoQU3RhcnRBY3Rpb25CeUdldBIoLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBopLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2UiABJ+ChdTdGFydEFjdGlvbkJ5R2V0QW5kV2FpdBIvLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QaMC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZSIAEl4KDVJlc3RhcnRBY3Rpb24SJS5vbGl2ZXRpbi5hcGkudjEuUmVzdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAElcKCktpbGxBY3Rpb24SIi5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlcXVlc3QaIy5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlc3BvbnNlIgASZgoPRXhlY3V0aW9uU3RhdHVzEicub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QaKC5vbGl2ZXRpbi5hcGkudjEuRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2UiABJOCgdHZXRMb2dzEh8ub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXF1ZXN0GiAub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXNwb25zZSIAEmAKDUdldEFjdGlvbkxvZ3MSJS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1JlcXVlc3QaJi5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1Jlc3BvbnNlIgASbAoRR2V0RXhlY3V0aW9uUXVldWUSKS5vbGl2ZXRpbi5hcGkudjEuR2V0RXhlY3V0aW9uUXVldWVSZXF1ZXN0Gioub2xpdmV0aW4uYXBpLnYxLkdldEV4ZWN1dGlvblF1ZXVlUmVzcG9uc2UiABJ1ChRWYWxpZGF0ZUFyZ3VtZW50VHlwZRIsLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlcXVlc3QaLS5vbGl2ZXRpbi5hcGkudjEuVmFsaWRhdGVBcmd1bWVudFR5cGVSZXNwb25zZSIAEksKBldob0FtSRIeLm9saXZldGluLmFwaS52MS5XaG9BbUlSZXF1ZXN0Gh8ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlc3BvbnNlIgASbAoRU2VydmVyRGlhZ25vc3RpY3MSKS5vbGl2ZXRpbi5hcGkudjEuU2VydmVyRGlhZ25vc3RpY3NSZXF1ZXN0Gioub2xpdmV0aW4uYXBpLnYxLlNlcnZlckRpYWdub3N0aWNzUmVzcG9uc2UiABJRCghEdW1wVmFycxIgLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1JlcXVlc3QaIS5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZSIAEngKFUR1bXBQdWJsaWNJZEFjdGlvbk1hcBItLm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXF1ZXN0Gi4ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlc3BvbnNlIgASVAoJR2V0UmVhZHl6EiEub2xpdmV0aW4uYXBpLnYxLkdldFJlYWR5elJlcXVlc3QaIi5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVzcG9uc2UiABJjCg5Mb2NhbFVzZXJMb2dpbhImLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuTG9jYWxVc2VyTG9naW5SZXNwb25zZSIAEl0KDFBhc3N3b3JkSGFzaBIkLm9saXZldGluLmFwaS52MS5QYXNzd29yZEhhc2hSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlc3BvbnNlIgASSwoGTG9nb3V0Eh4ub2xpdmV0aW4uYXBpLnYxLkxvZ291dFJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVzcG9uc2UiABJcCgtFdmVudFN0cmVhbRIjLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuRXZlbnRTdHJlYW1SZXNwb25zZSIAMAESYwoOR2V0RGlhZ25vc3RpY3MSJi5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXF1ZXN0Gicub2xpdmV0aW4uYXBpLnYxLkdldERpYWdub3N0aWNzUmVzcG9uc2UiABJFCgRJbml0Ehwub2xpdmV0aW4uYXBpLnYxLkluaXRSZXF1ZXN0Gh0ub2xpdmV0aW4uYXBpLnYxLkluaXRSZXNwb25zZSIAEmkKEEdldEFjdGlvbkJpbmRpbmcSKC5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uQmluZGluZ1Jlc3BvbnNlIgASWgoLR2V0RW50aXRpZXMSIy5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0aWVzUmVzcG9uc2UiABJJCglHZXRFbnRpdHkSIS5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXR5UmVxdWVzdBoXLm9saXZldGluLmFwaS52MS5FbnRpdHkiAEI4WjZnaXRodWIuY29tL09saXZlVGluL09saXZlVGluL2dlbi9vbGl2ZXRpbi9hcGkvdjE7YXBpdjFiBnByb3RvMw"); /** * Describes the message olivetin.api.v1.Action. @@ -493,75 +493,82 @@ export const InitRequestSchema = /*@__PURE__*/ export const InitResponseSchema = /*@__PURE__*/ messageDesc(file_olivetin_api_v1_olivetin, 68); +/** + * Describes the message olivetin.api.v1.RootDashboard. + * Use `create(RootDashboardSchema)` to create a new message. + */ +export const RootDashboardSchema = /*@__PURE__*/ + messageDesc(file_olivetin_api_v1_olivetin, 69); + /** * Describes the message olivetin.api.v1.AdditionalLink. * Use `create(AdditionalLinkSchema)` to create a new message. */ export const AdditionalLinkSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 69); + messageDesc(file_olivetin_api_v1_olivetin, 70); /** * Describes the message olivetin.api.v1.OAuth2Provider. * Use `create(OAuth2ProviderSchema)` to create a new message. */ export const OAuth2ProviderSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 70); + messageDesc(file_olivetin_api_v1_olivetin, 71); /** * Describes the message olivetin.api.v1.GetActionBindingRequest. * Use `create(GetActionBindingRequestSchema)` to create a new message. */ export const GetActionBindingRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 71); + messageDesc(file_olivetin_api_v1_olivetin, 72); /** * Describes the message olivetin.api.v1.GetActionBindingResponse. * Use `create(GetActionBindingResponseSchema)` to create a new message. */ export const GetActionBindingResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 72); + messageDesc(file_olivetin_api_v1_olivetin, 73); /** * Describes the message olivetin.api.v1.GetEntitiesRequest. * Use `create(GetEntitiesRequestSchema)` to create a new message. */ export const GetEntitiesRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 73); + messageDesc(file_olivetin_api_v1_olivetin, 74); /** * Describes the message olivetin.api.v1.GetEntitiesResponse. * Use `create(GetEntitiesResponseSchema)` to create a new message. */ export const GetEntitiesResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 74); + messageDesc(file_olivetin_api_v1_olivetin, 75); /** * Describes the message olivetin.api.v1.EntityDefinition. * Use `create(EntityDefinitionSchema)` to create a new message. */ export const EntityDefinitionSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 75); + messageDesc(file_olivetin_api_v1_olivetin, 76); /** * Describes the message olivetin.api.v1.EntityProperty. * Use `create(EntityPropertySchema)` to create a new message. */ export const EntityPropertySchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 76); + messageDesc(file_olivetin_api_v1_olivetin, 77); /** * Describes the message olivetin.api.v1.GetEntityRequest. * Use `create(GetEntityRequestSchema)` to create a new message. */ export const GetEntityRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 77); + messageDesc(file_olivetin_api_v1_olivetin, 78); /** * Describes the message olivetin.api.v1.RestartActionRequest. * Use `create(RestartActionRequestSchema)` to create a new message. */ export const RestartActionRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 78); + messageDesc(file_olivetin_api_v1_olivetin, 79); /** * @generated from service olivetin.api.v1.OliveTinApiService diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue index cc3a388..6ff12a2 100644 --- a/frontend/resources/vue/App.vue +++ b/frontend/resources/vue/App.vue @@ -338,40 +338,110 @@ function updateHeaderFromInit () { connectEventStreamIfNeeded() } +function getRootDashboardEntries () { + const entries = window.initResponse?.rootDashboardEntries + + if (entries && entries.length > 0) { + return entries.map((entry) => ({ + title: entry.title, + category: entry.category || '' + })) + } + + return (window.initResponse?.rootDashboards || []).map((title) => ({ + title, + category: '' + })) +} + +function addDashboardNavLink (title) { + navigation.value.addNavigationLink({ + id: title, + name: title, + title, + path: title === 'Actions' ? '/' : `/dashboards/${title}`, + icon: DashboardSquare01Icon + }) +} + +function addCategorizedDashboardLinks (entries) { + const categoryOrder = [] + const byCategory = new Map() + + for (const entry of entries) { + const category = entry.category.trim() + + if (!byCategory.has(category)) { + byCategory.set(category, []) + categoryOrder.push(category) + } + + byCategory.get(category).push(entry.title) + } + + for (const category of categoryOrder) { + navigation.value.addSection(category) + + for (const title of byCategory.get(category)) { + addDashboardNavLink(title) + } + } +} + function renderNavigation () { if (!navigation.value) { return } - const rootDashboards = window.initResponse?.rootDashboards || [] - if (typeof navigation.value.clearNavigationLinks === 'function') { navigation.value.clearNavigationLinks() } - for (const rootDashboard of rootDashboards) { - navigation.value.addNavigationLink({ - id: rootDashboard, - name: rootDashboard, - title: rootDashboard, - path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`, - icon: DashboardSquare01Icon - }) + const entries = getRootDashboardEntries() + const uncategorized = entries.filter((entry) => !entry.category.trim()) + const categorized = entries.filter((entry) => entry.category.trim()) + + for (const entry of uncategorized) { + addDashboardNavLink(entry.title) } - navigation.value.addSeparator() - navigation.value.addRouterLink('Entities', t('nav.entities')) + addCategorizedDashboardLinks(categorized) + addSystemNavLinks() +} + +function addSystemNavLinks () { + const systemLinks = [] + + systemLinks.push({ + routeName: 'Entities', + title: t('nav.entities') + }) if (showLogs.value) { - navigation.value.addRouterLink('Logs', t('nav.logs')) + systemLinks.push({ + routeName: 'Logs', + title: t('nav.logs') + }) } if (showDiagnostics.value) { const issueCount = window.initResponse?.configIssueCount || 0 - navigation.value.addRouterLink('Diagnostics', t('nav.diagnostics'), { - count: issueCount + systemLinks.push({ + routeName: 'Diagnostics', + title: t('nav.diagnostics'), + options: { count: issueCount } }) } + + if (systemLinks.length === 0) { + return + } + + navigation.value.addSection(t('nav.system')) + + for (const link of systemLinks) { + navigation.value.addRouterLink(link.routeName, link.title, link.options || {}) + } } function openLanguageDialog () { diff --git a/lang/combined_output.json b/lang/combined_output.json index 4db2148..f81310f 100644 --- a/lang/combined_output.json +++ b/lang/combined_output.json @@ -82,6 +82,7 @@ "nav.diagnostics": "Diagnostik", "nav.entities": "Entitäten", "nav.logs": "Protokolle", + "nav.system": "System", "raise-issue": "Ein Problem melden auf GitHub", "reconnecting": "Verbinde erneut…", "return-to-index": "Zurück zur Startseite", @@ -172,6 +173,7 @@ "nav.diagnostics": "Diagnostics", "nav.entities": "Entities", "nav.logs": "Logs", + "nav.system": "System", "raise-issue": "Raise an issue on GitHub", "reconnecting": "Reconnecting…", "return-to-index": "Return to index", @@ -262,6 +264,7 @@ "nav.diagnostics": "Diagnósticos", "nav.entities": "Entidades", "nav.logs": "Registros", + "nav.system": "Sistema", "raise-issue": "Reportar un problema en GitHub", "reconnecting": "Reconectando…", "return-to-index": "Volver a la página principal", @@ -352,6 +355,7 @@ "nav.diagnostics": "Diagnostica", "nav.entities": "Entità", "nav.logs": "Registri", + "nav.system": "Sistema", "raise-issue": "Segnala un problema su GitHub", "reconnecting": "Riconnessione…", "return-to-index": "Torna alla pagina principale", @@ -442,6 +446,7 @@ "nav.diagnostics": "诊断", "nav.entities": "实体", "nav.logs": "日志", + "nav.system": "系统", "raise-issue": "在 GitHub 上报告问题", "reconnecting": "正在重新连接…", "return-to-index": "返回首页", @@ -532,6 +537,7 @@ "nav.diagnostics": "診斷", "nav.entities": "實體", "nav.logs": "日誌", + "nav.system": "系統", "raise-issue": "在 GitHub 上建立 Issue", "reconnecting": "重新連線中…", "return-to-index": "返回首頁", diff --git a/lang/de-DE.yaml b/lang/de-DE.yaml index 6f00faf..9a6991f 100644 --- a/lang/de-DE.yaml +++ b/lang/de-DE.yaml @@ -5,6 +5,7 @@ translations: nav.logs: Protokolle nav.entities: Entitäten nav.diagnostics: Diagnostik + nav.system: System connected: Verbunden disconnected: Getrennt reconnecting: Verbinde erneut… diff --git a/lang/en.yaml b/lang/en.yaml index 9d1cc4c..a2cd664 100644 --- a/lang/en.yaml +++ b/lang/en.yaml @@ -7,6 +7,7 @@ translations: nav.logs: Logs nav.entities: Entities nav.diagnostics: Diagnostics + nav.system: System connected: Connected disconnected: Disconnected reconnecting: Reconnecting… diff --git a/lang/es-ES.yaml b/lang/es-ES.yaml index 1aa6752..6a4d805 100644 --- a/lang/es-ES.yaml +++ b/lang/es-ES.yaml @@ -5,6 +5,7 @@ translations: nav.logs: Registros nav.entities: Entidades nav.diagnostics: Diagnósticos + nav.system: Sistema connected: Conectado disconnected: Desconectado reconnecting: Reconectando… diff --git a/lang/it-IT.yaml b/lang/it-IT.yaml index 739552c..61e714e 100644 --- a/lang/it-IT.yaml +++ b/lang/it-IT.yaml @@ -5,6 +5,7 @@ translations: nav.logs: Registri nav.entities: Entità nav.diagnostics: Diagnostica + nav.system: Sistema docs: Documentazione connected: Connesso disconnected: Disconnesso diff --git a/lang/zh-Hans-CN.yaml b/lang/zh-Hans-CN.yaml index a2560e2..9f75a69 100644 --- a/lang/zh-Hans-CN.yaml +++ b/lang/zh-Hans-CN.yaml @@ -5,6 +5,7 @@ translations: nav.logs: 日志 nav.entities: 实体 nav.diagnostics: 诊断 + nav.system: 系统 connected: 已连接 disconnected: 已断开连接 reconnecting: 正在重新连接… diff --git a/lang/zh-Hant-TW.yaml b/lang/zh-Hant-TW.yaml index 462ffd7..20c5a0f 100644 --- a/lang/zh-Hant-TW.yaml +++ b/lang/zh-Hant-TW.yaml @@ -7,6 +7,7 @@ translations: nav.logs: 日誌 nav.entities: 實體 nav.diagnostics: 診斷 + nav.system: 系統 connected: 已連線 disconnected: 已斷線 reconnecting: 重新連線中… diff --git a/proto/olivetin/api/v1/olivetin.proto b/proto/olivetin/api/v1/olivetin.proto index 0d49b28..ab86b04 100644 --- a/proto/olivetin/api/v1/olivetin.proto +++ b/proto/olivetin/api/v1/olivetin.proto @@ -425,6 +425,12 @@ message InitResponse { repeated string available_themes = 24; // List of available theme names bool show_navigate_on_start_icons = 25; int32 config_issue_count = 26; + repeated RootDashboard root_dashboard_entries = 27; +} + +message RootDashboard { + string title = 1; + string category = 2; } message AdditionalLink { diff --git a/service/gen/olivetin/api/v1/olivetin.pb.go b/service/gen/olivetin/api/v1/olivetin.pb.go index ba38dd5..a3da7f4 100644 --- a/service/gen/olivetin/api/v1/olivetin.pb.go +++ b/service/gen/olivetin/api/v1/olivetin.pb.go @@ -4095,6 +4095,7 @@ type InitResponse struct { AvailableThemes []string `protobuf:"bytes,24,rep,name=available_themes,json=availableThemes,proto3" json:"available_themes,omitempty"` // List of available theme names ShowNavigateOnStartIcons bool `protobuf:"varint,25,opt,name=show_navigate_on_start_icons,json=showNavigateOnStartIcons,proto3" json:"show_navigate_on_start_icons,omitempty"` ConfigIssueCount int32 `protobuf:"varint,26,opt,name=config_issue_count,json=configIssueCount,proto3" json:"config_issue_count,omitempty"` + RootDashboardEntries []*RootDashboard `protobuf:"bytes,27,rep,name=root_dashboard_entries,json=rootDashboardEntries,proto3" json:"root_dashboard_entries,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4311,6 +4312,65 @@ func (x *InitResponse) GetConfigIssueCount() int32 { return 0 } +func (x *InitResponse) GetRootDashboardEntries() []*RootDashboard { + if x != nil { + return x.RootDashboardEntries + } + return nil +} + +type RootDashboard struct { + state protoimpl.MessageState `protogen:"open.v1"` + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + Category string `protobuf:"bytes,2,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RootDashboard) Reset() { + *x = RootDashboard{} + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RootDashboard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootDashboard) ProtoMessage() {} + +func (x *RootDashboard) ProtoReflect() protoreflect.Message { + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RootDashboard.ProtoReflect.Descriptor instead. +func (*RootDashboard) Descriptor() ([]byte, []int) { + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{69} +} + +func (x *RootDashboard) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *RootDashboard) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + type AdditionalLink struct { state protoimpl.MessageState `protogen:"open.v1"` Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` @@ -4321,7 +4381,7 @@ type AdditionalLink struct { func (x *AdditionalLink) Reset() { *x = AdditionalLink{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4333,7 +4393,7 @@ func (x *AdditionalLink) String() string { func (*AdditionalLink) ProtoMessage() {} func (x *AdditionalLink) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4346,7 +4406,7 @@ func (x *AdditionalLink) ProtoReflect() protoreflect.Message { // Deprecated: Use AdditionalLink.ProtoReflect.Descriptor instead. func (*AdditionalLink) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{69} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{70} } func (x *AdditionalLink) GetTitle() string { @@ -4374,7 +4434,7 @@ type OAuth2Provider struct { func (x *OAuth2Provider) Reset() { *x = OAuth2Provider{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[70] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4386,7 +4446,7 @@ func (x *OAuth2Provider) String() string { func (*OAuth2Provider) ProtoMessage() {} func (x *OAuth2Provider) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[70] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4399,7 +4459,7 @@ func (x *OAuth2Provider) ProtoReflect() protoreflect.Message { // Deprecated: Use OAuth2Provider.ProtoReflect.Descriptor instead. func (*OAuth2Provider) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{70} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{71} } func (x *OAuth2Provider) GetTitle() string { @@ -4432,7 +4492,7 @@ type GetActionBindingRequest struct { func (x *GetActionBindingRequest) Reset() { *x = GetActionBindingRequest{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[71] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4444,7 +4504,7 @@ func (x *GetActionBindingRequest) String() string { func (*GetActionBindingRequest) ProtoMessage() {} func (x *GetActionBindingRequest) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[71] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4457,7 +4517,7 @@ func (x *GetActionBindingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActionBindingRequest.ProtoReflect.Descriptor instead. func (*GetActionBindingRequest) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{71} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{72} } func (x *GetActionBindingRequest) GetBindingId() string { @@ -4477,7 +4537,7 @@ type GetActionBindingResponse struct { func (x *GetActionBindingResponse) Reset() { *x = GetActionBindingResponse{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[72] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4489,7 +4549,7 @@ func (x *GetActionBindingResponse) String() string { func (*GetActionBindingResponse) ProtoMessage() {} func (x *GetActionBindingResponse) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[72] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4502,7 +4562,7 @@ func (x *GetActionBindingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActionBindingResponse.ProtoReflect.Descriptor instead. func (*GetActionBindingResponse) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{72} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{73} } func (x *GetActionBindingResponse) GetAction() *Action { @@ -4531,7 +4591,7 @@ type GetEntitiesRequest struct { func (x *GetEntitiesRequest) Reset() { *x = GetEntitiesRequest{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[73] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4543,7 +4603,7 @@ func (x *GetEntitiesRequest) String() string { func (*GetEntitiesRequest) ProtoMessage() {} func (x *GetEntitiesRequest) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[73] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4556,7 +4616,7 @@ func (x *GetEntitiesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEntitiesRequest.ProtoReflect.Descriptor instead. func (*GetEntitiesRequest) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{73} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{74} } func (x *GetEntitiesRequest) GetEntityType() string { @@ -4596,7 +4656,7 @@ type GetEntitiesResponse struct { func (x *GetEntitiesResponse) Reset() { *x = GetEntitiesResponse{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[74] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4608,7 +4668,7 @@ func (x *GetEntitiesResponse) String() string { func (*GetEntitiesResponse) ProtoMessage() {} func (x *GetEntitiesResponse) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[74] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4621,7 +4681,7 @@ func (x *GetEntitiesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEntitiesResponse.ProtoReflect.Descriptor instead. func (*GetEntitiesResponse) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{74} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{75} } func (x *GetEntitiesResponse) GetEntityDefinitions() []*EntityDefinition { @@ -4645,7 +4705,7 @@ type EntityDefinition struct { func (x *EntityDefinition) Reset() { *x = EntityDefinition{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[75] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4657,7 +4717,7 @@ func (x *EntityDefinition) String() string { func (*EntityDefinition) ProtoMessage() {} func (x *EntityDefinition) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[75] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4670,7 +4730,7 @@ func (x *EntityDefinition) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityDefinition.ProtoReflect.Descriptor instead. func (*EntityDefinition) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{75} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{76} } func (x *EntityDefinition) GetTitle() string { @@ -4725,7 +4785,7 @@ type EntityProperty struct { func (x *EntityProperty) Reset() { *x = EntityProperty{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[76] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4737,7 +4797,7 @@ func (x *EntityProperty) String() string { func (*EntityProperty) ProtoMessage() {} func (x *EntityProperty) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[76] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4750,7 +4810,7 @@ func (x *EntityProperty) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityProperty.ProtoReflect.Descriptor instead. func (*EntityProperty) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{76} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{77} } func (x *EntityProperty) GetName() string { @@ -4777,7 +4837,7 @@ type GetEntityRequest struct { func (x *GetEntityRequest) Reset() { *x = GetEntityRequest{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[77] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4789,7 +4849,7 @@ func (x *GetEntityRequest) String() string { func (*GetEntityRequest) ProtoMessage() {} func (x *GetEntityRequest) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[77] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4802,7 +4862,7 @@ func (x *GetEntityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEntityRequest.ProtoReflect.Descriptor instead. func (*GetEntityRequest) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{77} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{78} } func (x *GetEntityRequest) GetUniqueKey() string { @@ -4828,7 +4888,7 @@ type RestartActionRequest struct { func (x *RestartActionRequest) Reset() { *x = RestartActionRequest{} - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[78] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4840,7 +4900,7 @@ func (x *RestartActionRequest) String() string { func (*RestartActionRequest) ProtoMessage() {} func (x *RestartActionRequest) ProtoReflect() protoreflect.Message { - mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[78] + mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4853,7 +4913,7 @@ func (x *RestartActionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartActionRequest.ProtoReflect.Descriptor instead. func (*RestartActionRequest) Descriptor() ([]byte, []int) { - return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{78} + return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{79} } func (x *RestartActionRequest) GetExecutionTrackingId() string { @@ -5175,7 +5235,8 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" + "\vSshFoundKey\x18\x01 \x01(\tR\vSshFoundKey\x12&\n" + "\x0eSshFoundConfig\x18\x02 \x01(\tR\x0eSshFoundConfig\x12A\n" + "\rconfig_issues\x18\x03 \x03(\v2\x1c.olivetin.api.v1.ConfigIssueR\fconfigIssues\"\r\n" + - "\vInitRequest\"\xbb\t\n" + + "\vInitRequest\"\x91\n" + + "\n" + "\fInitResponse\x12\x1e\n" + "\n" + "showFooter\x18\x01 \x01(\bR\n" + @@ -5206,7 +5267,11 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" + "\x0elogin_required\x18\x17 \x01(\bR\rloginRequired\x12)\n" + "\x10available_themes\x18\x18 \x03(\tR\x0favailableThemes\x12>\n" + "\x1cshow_navigate_on_start_icons\x18\x19 \x01(\bR\x18showNavigateOnStartIcons\x12,\n" + - "\x12config_issue_count\x18\x1a \x01(\x05R\x10configIssueCount\"8\n" + + "\x12config_issue_count\x18\x1a \x01(\x05R\x10configIssueCount\x12T\n" + + "\x16root_dashboard_entries\x18\x1b \x03(\v2\x1e.olivetin.api.v1.RootDashboardR\x14rootDashboardEntries\"A\n" + + "\rRootDashboard\x12\x14\n" + + "\x05title\x18\x01 \x01(\tR\x05title\x12\x1a\n" + + "\bcategory\x18\x02 \x01(\tR\bcategory\"8\n" + "\x0eAdditionalLink\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12\x10\n" + "\x03url\x18\x02 \x01(\tR\x03url\"L\n" + @@ -5287,7 +5352,7 @@ func file_olivetin_api_v1_olivetin_proto_rawDescGZIP() []byte { return file_olivetin_api_v1_olivetin_proto_rawDescData } -var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 86) +var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 87) var file_olivetin_api_v1_olivetin_proto_goTypes = []any{ (*Action)(nil), // 0: olivetin.api.v1.Action (*ActionGroupMembership)(nil), // 1: olivetin.api.v1.ActionGroupMembership @@ -5358,35 +5423,36 @@ var file_olivetin_api_v1_olivetin_proto_goTypes = []any{ (*GetDiagnosticsResponse)(nil), // 66: olivetin.api.v1.GetDiagnosticsResponse (*InitRequest)(nil), // 67: olivetin.api.v1.InitRequest (*InitResponse)(nil), // 68: olivetin.api.v1.InitResponse - (*AdditionalLink)(nil), // 69: olivetin.api.v1.AdditionalLink - (*OAuth2Provider)(nil), // 70: olivetin.api.v1.OAuth2Provider - (*GetActionBindingRequest)(nil), // 71: olivetin.api.v1.GetActionBindingRequest - (*GetActionBindingResponse)(nil), // 72: olivetin.api.v1.GetActionBindingResponse - (*GetEntitiesRequest)(nil), // 73: olivetin.api.v1.GetEntitiesRequest - (*GetEntitiesResponse)(nil), // 74: olivetin.api.v1.GetEntitiesResponse - (*EntityDefinition)(nil), // 75: olivetin.api.v1.EntityDefinition - (*EntityProperty)(nil), // 76: olivetin.api.v1.EntityProperty - (*GetEntityRequest)(nil), // 77: olivetin.api.v1.GetEntityRequest - (*RestartActionRequest)(nil), // 78: olivetin.api.v1.RestartActionRequest - nil, // 79: olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry - nil, // 80: olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry - nil, // 81: olivetin.api.v1.ActionArgument.SuggestionsEntry - nil, // 82: olivetin.api.v1.EntityRelatedAction.PrefilledArgumentsEntry - nil, // 83: olivetin.api.v1.Entity.FieldsEntry - nil, // 84: olivetin.api.v1.DumpVarsResponse.ContentsEntry - nil, // 85: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry + (*RootDashboard)(nil), // 69: olivetin.api.v1.RootDashboard + (*AdditionalLink)(nil), // 70: olivetin.api.v1.AdditionalLink + (*OAuth2Provider)(nil), // 71: olivetin.api.v1.OAuth2Provider + (*GetActionBindingRequest)(nil), // 72: olivetin.api.v1.GetActionBindingRequest + (*GetActionBindingResponse)(nil), // 73: olivetin.api.v1.GetActionBindingResponse + (*GetEntitiesRequest)(nil), // 74: olivetin.api.v1.GetEntitiesRequest + (*GetEntitiesResponse)(nil), // 75: olivetin.api.v1.GetEntitiesResponse + (*EntityDefinition)(nil), // 76: olivetin.api.v1.EntityDefinition + (*EntityProperty)(nil), // 77: olivetin.api.v1.EntityProperty + (*GetEntityRequest)(nil), // 78: olivetin.api.v1.GetEntityRequest + (*RestartActionRequest)(nil), // 79: olivetin.api.v1.RestartActionRequest + nil, // 80: olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry + nil, // 81: olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry + nil, // 82: olivetin.api.v1.ActionArgument.SuggestionsEntry + nil, // 83: olivetin.api.v1.EntityRelatedAction.PrefilledArgumentsEntry + nil, // 84: olivetin.api.v1.Entity.FieldsEntry + nil, // 85: olivetin.api.v1.DumpVarsResponse.ContentsEntry + nil, // 86: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry } var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{ 3, // 0: olivetin.api.v1.Action.arguments:type_name -> olivetin.api.v1.ActionArgument 2, // 1: olivetin.api.v1.Action.exec_on_webhooks:type_name -> olivetin.api.v1.ActionWebhookExecHint 1, // 2: olivetin.api.v1.Action.groups:type_name -> olivetin.api.v1.ActionGroupMembership - 79, // 3: olivetin.api.v1.ActionWebhookExecHint.match_headers:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry - 80, // 4: olivetin.api.v1.ActionWebhookExecHint.match_query:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry + 80, // 3: olivetin.api.v1.ActionWebhookExecHint.match_headers:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry + 81, // 4: olivetin.api.v1.ActionWebhookExecHint.match_query:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry 4, // 5: olivetin.api.v1.ActionArgument.choices:type_name -> olivetin.api.v1.ActionArgumentChoice - 81, // 6: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry + 82, // 6: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry 0, // 7: olivetin.api.v1.EntityRelatedAction.action:type_name -> olivetin.api.v1.Action - 82, // 8: olivetin.api.v1.EntityRelatedAction.prefilled_arguments:type_name -> olivetin.api.v1.EntityRelatedAction.PrefilledArgumentsEntry - 83, // 9: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry + 83, // 8: olivetin.api.v1.EntityRelatedAction.prefilled_arguments:type_name -> olivetin.api.v1.EntityRelatedAction.PrefilledArgumentsEntry + 84, // 9: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry 5, // 10: olivetin.api.v1.Entity.related_actions:type_name -> olivetin.api.v1.EntityRelatedAction 10, // 11: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard 11, // 12: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent @@ -5404,8 +5470,8 @@ var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{ 28, // 24: olivetin.api.v1.GetExecutionQueueResponse.groups:type_name -> olivetin.api.v1.ExecutionQueueGroup 22, // 25: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry 35, // 26: olivetin.api.v1.ExecutionStatusResponse.back_to_dashboards:type_name -> olivetin.api.v1.DashboardNavigationTarget - 84, // 27: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry - 85, // 28: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry + 85, // 27: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry + 86, // 28: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry 51, // 29: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged 52, // 30: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged 54, // 31: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished @@ -5415,72 +5481,73 @@ var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{ 22, // 35: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry 22, // 36: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry 65, // 37: olivetin.api.v1.GetDiagnosticsResponse.config_issues:type_name -> olivetin.api.v1.ConfigIssue - 70, // 38: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider - 69, // 39: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink + 71, // 38: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider + 70, // 39: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink 8, // 40: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy - 0, // 41: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action - 35, // 42: olivetin.api.v1.GetActionBindingResponse.back_to_dashboards:type_name -> olivetin.api.v1.DashboardNavigationTarget - 75, // 43: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition - 6, // 44: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity - 76, // 45: olivetin.api.v1.EntityDefinition.properties:type_name -> olivetin.api.v1.EntityProperty - 43, // 46: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding - 9, // 47: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest - 12, // 48: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest - 15, // 49: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest - 17, // 50: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest - 19, // 51: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest - 78, // 52: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest - 56, // 53: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest - 34, // 54: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest - 21, // 55: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest - 24, // 56: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest - 26, // 57: olivetin.api.v1.OliveTinApiService.GetExecutionQueue:input_type -> olivetin.api.v1.GetExecutionQueueRequest - 30, // 58: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest - 37, // 59: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest - 39, // 60: olivetin.api.v1.OliveTinApiService.ServerDiagnostics:input_type -> olivetin.api.v1.ServerDiagnosticsRequest - 41, // 61: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest - 44, // 62: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest - 46, // 63: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest - 58, // 64: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest - 60, // 65: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest - 62, // 66: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest - 48, // 67: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest - 64, // 68: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest - 67, // 69: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest - 71, // 70: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest - 73, // 71: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest - 77, // 72: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest - 7, // 73: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse - 14, // 74: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse - 16, // 75: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse - 18, // 76: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse - 20, // 77: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse - 14, // 78: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse - 57, // 79: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse - 36, // 80: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse - 23, // 81: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse - 25, // 82: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse - 29, // 83: olivetin.api.v1.OliveTinApiService.GetExecutionQueue:output_type -> olivetin.api.v1.GetExecutionQueueResponse - 31, // 84: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse - 38, // 85: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse - 40, // 86: olivetin.api.v1.OliveTinApiService.ServerDiagnostics:output_type -> olivetin.api.v1.ServerDiagnosticsResponse - 42, // 87: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse - 45, // 88: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse - 47, // 89: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse - 59, // 90: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse - 61, // 91: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse - 63, // 92: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse - 49, // 93: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse - 66, // 94: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse - 68, // 95: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse - 72, // 96: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse - 74, // 97: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse - 6, // 98: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity - 73, // [73:99] is the sub-list for method output_type - 47, // [47:73] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 69, // 41: olivetin.api.v1.InitResponse.root_dashboard_entries:type_name -> olivetin.api.v1.RootDashboard + 0, // 42: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action + 35, // 43: olivetin.api.v1.GetActionBindingResponse.back_to_dashboards:type_name -> olivetin.api.v1.DashboardNavigationTarget + 76, // 44: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition + 6, // 45: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity + 77, // 46: olivetin.api.v1.EntityDefinition.properties:type_name -> olivetin.api.v1.EntityProperty + 43, // 47: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding + 9, // 48: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest + 12, // 49: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest + 15, // 50: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest + 17, // 51: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest + 19, // 52: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest + 79, // 53: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest + 56, // 54: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest + 34, // 55: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest + 21, // 56: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest + 24, // 57: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest + 26, // 58: olivetin.api.v1.OliveTinApiService.GetExecutionQueue:input_type -> olivetin.api.v1.GetExecutionQueueRequest + 30, // 59: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest + 37, // 60: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest + 39, // 61: olivetin.api.v1.OliveTinApiService.ServerDiagnostics:input_type -> olivetin.api.v1.ServerDiagnosticsRequest + 41, // 62: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest + 44, // 63: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest + 46, // 64: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest + 58, // 65: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest + 60, // 66: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest + 62, // 67: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest + 48, // 68: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest + 64, // 69: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest + 67, // 70: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest + 72, // 71: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest + 74, // 72: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest + 78, // 73: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest + 7, // 74: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse + 14, // 75: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse + 16, // 76: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse + 18, // 77: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse + 20, // 78: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse + 14, // 79: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse + 57, // 80: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse + 36, // 81: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse + 23, // 82: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse + 25, // 83: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse + 29, // 84: olivetin.api.v1.OliveTinApiService.GetExecutionQueue:output_type -> olivetin.api.v1.GetExecutionQueueResponse + 31, // 85: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse + 38, // 86: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse + 40, // 87: olivetin.api.v1.OliveTinApiService.ServerDiagnostics:output_type -> olivetin.api.v1.ServerDiagnosticsResponse + 42, // 88: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse + 45, // 89: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse + 47, // 90: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse + 59, // 91: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse + 61, // 92: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse + 63, // 93: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse + 49, // 94: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse + 66, // 95: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse + 68, // 96: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse + 73, // 97: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse + 75, // 98: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse + 6, // 99: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity + 74, // [74:100] is the sub-list for method output_type + 48, // [48:74] is the sub-list for method input_type + 48, // [48:48] is the sub-list for extension type_name + 48, // [48:48] is the sub-list for extension extendee + 0, // [0:48] is the sub-list for field type_name } func init() { file_olivetin_api_v1_olivetin_proto_init() } @@ -5502,7 +5569,7 @@ func file_olivetin_api_v1_olivetin_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_olivetin_api_v1_olivetin_proto_rawDesc), len(file_olivetin_api_v1_olivetin_proto_rawDesc)), NumEnums: 0, - NumMessages: 86, + NumMessages: 87, NumExtensions: 0, NumServices: 1, }, diff --git a/service/internal/config/config.go b/service/internal/config/config.go index 5abc54e..59e6f8e 100644 --- a/service/internal/config/config.go +++ b/service/internal/config/config.go @@ -289,6 +289,7 @@ type LogDebugOptions struct { type DashboardComponent struct { Title string `koanf:"title"` + Category string `koanf:"category"` Type string `koanf:"type"` Entity string `koanf:"entity"` Icon string `koanf:"icon"` diff --git a/service/internal/config/config_reloader.go b/service/internal/config/config_reloader.go index 0927387..a3f92be 100644 --- a/service/internal/config/config_reloader.go +++ b/service/internal/config/config_reloader.go @@ -2,11 +2,13 @@ package config import ( "fmt" + "net" "os" "path/filepath" "reflect" "regexp" "sort" + "strconv" "strings" "github.com/OliveTin/OliveTin/internal/configissues" @@ -81,12 +83,50 @@ func afterLoadFinalize(cfg *Config, configPath string) { cfg.SetDir(filepath.Dir(configPath)) cfg.Sanitize() + applyPortEnvironmentOverride(cfg) for _, l := range listeners { l() } } +// applyPortEnvironmentOverride sets the single HTTP frontend listen port from +// $PORT when that environment variable is set. This runs after config unmarshal +// so PORT wins over listenAddressSingleHTTPFrontend in config.yaml (common on +// Heroku, Cloud Run, and similar hosts). When PORT is unset, the config value +// or the default (1337) is left unchanged. +func applyPortEnvironmentOverride(cfg *Config) { + envPort := strings.TrimSpace(os.Getenv("PORT")) + if envPort == "" { + return + } + + port, err := strconv.Atoi(envPort) + if err != nil || port < 1 || port > 65535 { + log.WithFields(log.Fields{ + "PORT": envPort, + "error": err, + }).Error("Ignoring invalid PORT environment variable") + return + } + + host := listenHostOrDefault(cfg.ListenAddressSingleHTTPFrontend) + cfg.ListenAddressSingleHTTPFrontend = net.JoinHostPort(host, strconv.Itoa(port)) + + log.WithFields(log.Fields{ + "address": cfg.ListenAddressSingleHTTPFrontend, + }).Info("Using PORT environment variable for single HTTP frontend listen address") +} + +func listenHostOrDefault(listenAddress string) string { + host, _, err := net.SplitHostPort(listenAddress) + if err != nil || host == "" { + return "0.0.0.0" + } + + return host +} + // buildIncludePath constructs the full path to the include directory. func buildIncludePath(k *koanf.Koanf, baseConfigPath string) string { relativeIncludePath := k.String("include")