diff --git a/config.yaml b/config.yaml index ee4c6ba..5e3613a 100644 --- a/config.yaml +++ b/config.yaml @@ -189,12 +189,24 @@ actions: - 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: entity: server - title: "{{ server.name }} Power Off" shell: "echo 'Power Off Server: {{ server.hostname }}'" + icon: + entity: server + + - title: "{{ server.name }} Print server name" + shell: 'echo "Server name: {{ server.name }}"' entity: server - title: Ping All Servers @@ -278,6 +290,11 @@ dashboards: 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. @@ -299,6 +316,11 @@ dashboards: - 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: 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 4484806..f5b81ae 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 @@ -146,6 +146,11 @@ export declare type Entity = Message<"olivetin.api.v1.Entity"> & { * @generated from field: string type = 3; */ type: string; + + /** + * @generated from field: repeated string directories = 4; + */ + directories: string[]; }; /** @@ -204,6 +209,16 @@ export declare type GetDashboardRequest = Message<"olivetin.api.v1.GetDashboardR * @generated from field: string title = 1; */ title: string; + + /** + * @generated from field: string entity_type = 2; + */ + entityType: string; + + /** + * @generated from field: string entity_key = 3; + */ + entityKey: string; }; /** @@ -266,6 +281,16 @@ export declare type DashboardComponent = Message<"olivetin.api.v1.DashboardCompo * @generated from field: olivetin.api.v1.Action action = 6; */ action?: Action; + + /** + * @generated from field: string entity_type = 7; + */ + entityType: string; + + /** + * @generated from field: string entity_key = 8; + */ + entityKey: string; }; /** 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 6693a78..4f8874d 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("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSK3AQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBSKaAgoOQWN0aW9uQXJndW1lbnQSDAoEbmFtZRgBIAEoCRINCgV0aXRsZRgCIAEoCRIMCgR0eXBlGAMgASgJEhUKDWRlZmF1bHRfdmFsdWUYBCABKAkSNgoHY2hvaWNlcxgFIAMoCzIlLm9saXZldGluLmFwaS52MS5BY3Rpb25Bcmd1bWVudENob2ljZRITCgtkZXNjcmlwdGlvbhgGIAEoCRJFCgtzdWdnZXN0aW9ucxgHIAMoCzIwLm9saXZldGluLmFwaS52MS5BY3Rpb25Bcmd1bWVudC5TdWdnZXN0aW9uc0VudHJ5GjIKEFN1Z2dlc3Rpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI0ChRBY3Rpb25Bcmd1bWVudENob2ljZRINCgV2YWx1ZRgBIAEoCRINCgV0aXRsZRgCIAEoCSI5CgZFbnRpdHkSDQoFdGl0bGUYASABKAkSEgoKdW5pcXVlX2tleRgCIAEoCRIMCgR0eXBlGAMgASgJIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiQgoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCCIkChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJIlEKCURhc2hib2FyZBINCgV0aXRsZRgBIAEoCRI1Cghjb250ZW50cxgCIAMoCzIjLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmRDb21wb25lbnQisgEKEkRhc2hib2FyZENvbXBvbmVudBINCgV0aXRsZRgBIAEoCRIMCgR0eXBlGAIgASgJEjUKCGNvbnRlbnRzGAMgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudBIMCgRpY29uGAQgASgJEhEKCWNzc19jbGFzcxgFIAEoCRInCgZhY3Rpb24YBiABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uIn0KElN0YXJ0QWN0aW9uUmVxdWVzdBISCgpiaW5kaW5nX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50EhoKEnVuaXF1ZV90cmFja2luZ19pZBgDIAEoCSIyChNTdGFydEFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkiNAoTU3RhcnRBY3Rpb25SZXNwb25zZRIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYAiABKAkiZwoZU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSNwoJYXJndW1lbnRzGAIgAygLMiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQXJndW1lbnQiSgoaU3RhcnRBY3Rpb25BbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IiwKF1N0YXJ0QWN0aW9uQnlHZXRSZXF1ZXN0EhEKCWFjdGlvbl9pZBgBIAEoCSI5ChhTdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIjMKHlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiTwofU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZRIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiJgoOR2V0TG9nc1JlcXVlc3QSFAoMc3RhcnRfb2Zmc2V0GAEgASgDIvQCCghMb2dFbnRyeRIYChBkYXRldGltZV9zdGFydGVkGAEgASgJEhQKDGFjdGlvbl90aXRsZRgCIAEoCRIOCgZvdXRwdXQYAyABKAkSEQoJdGltZWRfb3V0GAUgASgIEhEKCWV4aXRfY29kZRgGIAEoBRIMCgR1c2VyGAcgASgJEhIKCnVzZXJfY2xhc3MYCCABKAkSEwoLYWN0aW9uX2ljb24YCSABKAkSDAoEdGFncxgKIAMoCRIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYCyABKAkSGQoRZGF0ZXRpbWVfZmluaXNoZWQYDCABKAkSEQoJYWN0aW9uX2lkGA0gASgJEhkKEWV4ZWN1dGlvbl9zdGFydGVkGA4gASgIEhoKEmV4ZWN1dGlvbl9maW5pc2hlZBgPIAEoCBIPCgdibG9ja2VkGBAgASgIEhYKDmRhdGV0aW1lX2luZGV4GBEgASgDEhAKCGNhbl9raWxsGBIgASgIIpEBCg9HZXRMb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI/ChRHZXRBY3Rpb25Mb2dzUmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSFAoMc3RhcnRfb2Zmc2V0GAIgASgDIpcBChVHZXRBY3Rpb25Mb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI6ChtWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlcXVlc3QSDQoFdmFsdWUYASABKAkSDAoEdHlwZRgCIAEoCSJCChxWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlEg0KBXZhbGlkGAEgASgIEhMKC2Rlc2NyaXB0aW9uGAIgASgJIjYKFVdhdGNoRXhlY3V0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkiJgoUV2F0Y2hFeGVjdXRpb25VcGRhdGUSDgoGdXBkYXRlGAEgASgJIkoKFkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEhEKCWFjdGlvbl9pZBgCIAEoCSJHChdFeGVjdXRpb25TdGF0dXNSZXNwb25zZRIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiDwoNV2hvQW1JUmVxdWVzdCJsCg5XaG9BbUlSZXNwb25zZRIaChJhdXRoZW50aWNhdGVkX3VzZXIYASABKAkSEQoJdXNlcmdyb3VwGAIgASgJEhAKCHByb3ZpZGVyGAMgASgJEgwKBGFjbHMYBCADKAkSCwoDc2lkGAUgASgJIhIKEFNvc1JlcG9ydFJlcXVlc3QiIgoRU29zUmVwb3J0UmVzcG9uc2USDQoFYWxlcnQYASABKAkiEQoPRHVtcFZhcnNSZXF1ZXN0IpUBChBEdW1wVmFyc1Jlc3BvbnNlEg0KBWFsZXJ0GAEgASgJEkEKCGNvbnRlbnRzGAIgAygLMi8ub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVzcG9uc2UuQ29udGVudHNFbnRyeRovCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiPwoQQWN0aW9uRW50aXR5UGFpchIUCgxhY3Rpb25fdGl0bGUYASABKAkSFQoNZW50aXR5X3ByZWZpeBgCIAEoCSIeChxEdW1wUHVibGljSWRBY3Rpb25NYXBSZXF1ZXN0ItIBCh1EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZRINCgVhbGVydBgBIAEoCRJOCghjb250ZW50cxgCIAMoCzI8Lm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZS5Db250ZW50c0VudHJ5GlIKDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEjAKBXZhbHVlGAIgASgLMiEub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkVudGl0eVBhaXI6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCLjAgoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiRQoWR2V0RGlhZ25vc3RpY3NSZXNwb25zZRITCgtTc2hGb3VuZEtleRgBIAEoCRIWCg5Tc2hGb3VuZENvbmZpZxgCIAEoCSINCgtJbml0UmVxdWVzdCKrBQoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIIiwKDkFkZGl0aW9uYWxMaW5rEg0KBXRpdGxlGAEgASgJEgsKA3VybBgCIAEoCSI6Cg5PQXV0aDJQcm92aWRlchINCgV0aXRsZRgBIAEoCRIMCgRpY29uGAMgASgJEgsKA2tleRgEIAEoCSItChdHZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBISCgpiaW5kaW5nX2lkGAEgASgJIkMKGEdldEFjdGlvbkJpbmRpbmdSZXNwb25zZRInCgZhY3Rpb24YASABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uIhQKEkdldEVudGl0aWVzUmVxdWVzdCJUChNHZXRFbnRpdGllc1Jlc3BvbnNlEj0KEmVudGl0eV9kZWZpbml0aW9ucxgBIAMoCzIhLm9saXZldGluLmFwaS52MS5FbnRpdHlEZWZpbml0aW9uImkKEEVudGl0eURlZmluaXRpb24SDQoFdGl0bGUYASABKAkSKgoJaW5zdGFuY2VzGAIgAygLMhcub2xpdmV0aW4uYXBpLnYxLkVudGl0eRIaChJ1c2VkX29uX2Rhc2hib2FyZHMYAyADKAkiNAoQR2V0RW50aXR5UmVxdWVzdBISCgp1bmlxdWVfa2V5GAEgASgJEgwKBHR5cGUYAiABKAkiNQoUUmVzdGFydEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJMugSChJPbGl2ZVRpbkFwaVNlcnZpY2USXQoMR2V0RGFzaGJvYXJkEiQub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuR2V0RGFzaGJvYXJkUmVzcG9uc2UiABJaCgtTdGFydEFjdGlvbhIjLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAEm8KElN0YXJ0QWN0aW9uQW5kV2FpdBIqLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXF1ZXN0Gisub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlIgASaQoQU3RhcnRBY3Rpb25CeUdldBIoLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBopLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2UiABJ+ChdTdGFydEFjdGlvbkJ5R2V0QW5kV2FpdBIvLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QaMC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZSIAEl4KDVJlc3RhcnRBY3Rpb24SJS5vbGl2ZXRpbi5hcGkudjEuUmVzdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAElcKCktpbGxBY3Rpb24SIi5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlcXVlc3QaIy5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlc3BvbnNlIgASZgoPRXhlY3V0aW9uU3RhdHVzEicub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QaKC5vbGl2ZXRpbi5hcGkudjEuRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2UiABJOCgdHZXRMb2dzEh8ub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXF1ZXN0GiAub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXNwb25zZSIAEmAKDUdldEFjdGlvbkxvZ3MSJS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1JlcXVlc3QaJi5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1Jlc3BvbnNlIgASdQoUVmFsaWRhdGVBcmd1bWVudFR5cGUSLC5vbGl2ZXRpbi5hcGkudjEuVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Gi0ub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2UiABJLCgZXaG9BbUkSHi5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVxdWVzdBofLm9saXZldGluLmFwaS52MS5XaG9BbUlSZXNwb25zZSIAElQKCVNvc1JlcG9ydBIhLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLlNvc1JlcG9ydFJlc3BvbnNlIgASUQoIRHVtcFZhcnMSIC5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXF1ZXN0GiEub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVzcG9uc2UiABJ4ChVEdW1wUHVibGljSWRBY3Rpb25NYXASLS5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdBouLm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZSIAElQKCUdldFJlYWR5ehIhLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLkdldFJlYWR5elJlc3BvbnNlIgASYwoOTG9jYWxVc2VyTG9naW4SJi5vbGl2ZXRpbi5hcGkudjEuTG9jYWxVc2VyTG9naW5SZXF1ZXN0Gicub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVzcG9uc2UiABJdCgxQYXNzd29yZEhhc2gSJC5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVxdWVzdBolLm9saXZldGluLmFwaS52MS5QYXNzd29yZEhhc2hSZXNwb25zZSIAEksKBkxvZ291dBIeLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXF1ZXN0Gh8ub2xpdmV0aW4uYXBpLnYxLkxvZ291dFJlc3BvbnNlIgASXAoLRXZlbnRTdHJlYW0SIy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRTdHJlYW1SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVzcG9uc2UiADABEmMKDkdldERpYWdub3N0aWNzEiYub2xpdmV0aW4uYXBpLnYxLkdldERpYWdub3N0aWNzUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1Jlc3BvbnNlIgASRQoESW5pdBIcLm9saXZldGluLmFwaS52MS5Jbml0UmVxdWVzdBodLm9saXZldGluLmFwaS52MS5Jbml0UmVzcG9uc2UiABJpChBHZXRBY3Rpb25CaW5kaW5nEigub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXF1ZXN0Gikub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXNwb25zZSIAEloKC0dldEVudGl0aWVzEiMub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0aWVzUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1Jlc3BvbnNlIgASSQoJR2V0RW50aXR5EiEub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0eVJlcXVlc3QaFy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5IgBCOFo2Z2l0aHViLmNvbS9PbGl2ZVRpbi9PbGl2ZVRpbi9nZW4vb2xpdmV0aW4vYXBpL3YxO2FwaXYxYgZwcm90bzM"); + fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSK3AQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBSKaAgoOQWN0aW9uQXJndW1lbnQSDAoEbmFtZRgBIAEoCRINCgV0aXRsZRgCIAEoCRIMCgR0eXBlGAMgASgJEhUKDWRlZmF1bHRfdmFsdWUYBCABKAkSNgoHY2hvaWNlcxgFIAMoCzIlLm9saXZldGluLmFwaS52MS5BY3Rpb25Bcmd1bWVudENob2ljZRITCgtkZXNjcmlwdGlvbhgGIAEoCRJFCgtzdWdnZXN0aW9ucxgHIAMoCzIwLm9saXZldGluLmFwaS52MS5BY3Rpb25Bcmd1bWVudC5TdWdnZXN0aW9uc0VudHJ5GjIKEFN1Z2dlc3Rpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI0ChRBY3Rpb25Bcmd1bWVudENob2ljZRINCgV2YWx1ZRgBIAEoCRINCgV0aXRsZRgCIAEoCSJOCgZFbnRpdHkSDQoFdGl0bGUYASABKAkSEgoKdW5pcXVlX2tleRgCIAEoCRIMCgR0eXBlGAMgASgJEhMKC2RpcmVjdG9yaWVzGAQgAygJIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiQgoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IiYKDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAyL0AgoITG9nRW50cnkSGAoQZGF0ZXRpbWVfc3RhcnRlZBgBIAEoCRIUCgxhY3Rpb25fdGl0bGUYAiABKAkSDgoGb3V0cHV0GAMgASgJEhEKCXRpbWVkX291dBgFIAEoCBIRCglleGl0X2NvZGUYBiABKAUSDAoEdXNlchgHIAEoCRISCgp1c2VyX2NsYXNzGAggASgJEhMKC2FjdGlvbl9pY29uGAkgASgJEgwKBHRhZ3MYCiADKAkSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAsgASgJEhkKEWRhdGV0aW1lX2ZpbmlzaGVkGAwgASgJEhEKCWFjdGlvbl9pZBgNIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCCKRAQoPR2V0TG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiPwoUR2V0QWN0aW9uTG9nc1JlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEhQKDHN0YXJ0X29mZnNldBgCIAEoAyKXAQoVR2V0QWN0aW9uTG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiOgobVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Eg0KBXZhbHVlGAEgASgJEgwKBHR5cGUYAiABKAkiQgocVmFsaWRhdGVBcmd1bWVudFR5cGVSZXNwb25zZRINCgV2YWxpZBgBIAEoCBITCgtkZXNjcmlwdGlvbhgCIAEoCSI2ChVXYXRjaEV4ZWN1dGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJIiYKFFdhdGNoRXhlY3V0aW9uVXBkYXRlEg4KBnVwZGF0ZRgBIAEoCSJKChZFeGVjdXRpb25TdGF0dXNSZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIRCglhY3Rpb25faWQYAiABKAkiRwoXRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ig8KDVdob0FtSVJlcXVlc3QibAoOV2hvQW1JUmVzcG9uc2USGgoSYXV0aGVudGljYXRlZF91c2VyGAEgASgJEhEKCXVzZXJncm91cBgCIAEoCRIQCghwcm92aWRlchgDIAEoCRIMCgRhY2xzGAQgAygJEgsKA3NpZBgFIAEoCSISChBTb3NSZXBvcnRSZXF1ZXN0IiIKEVNvc1JlcG9ydFJlc3BvbnNlEg0KBWFsZXJ0GAEgASgJIhEKD0R1bXBWYXJzUmVxdWVzdCKVAQoQRHVtcFZhcnNSZXNwb25zZRINCgVhbGVydBgBIAEoCRJBCghjb250ZW50cxgCIAMoCzIvLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlLkNvbnRlbnRzRW50cnkaLwoNQ29udGVudHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIj8KEEFjdGlvbkVudGl0eVBhaXISFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLSAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpSCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIwCgV2YWx1ZRgCIAEoCzIhLm9saXZldGluLmFwaS52MS5BY3Rpb25FbnRpdHlQYWlyOgI4ASISChBHZXRSZWFkeXpSZXF1ZXN0IiMKEUdldFJlYWR5elJlc3BvbnNlEg4KBnN0YXR1cxgBIAEoCSIUChJFdmVudFN0cmVhbVJlcXVlc3Qi4wIKE0V2ZW50U3RyZWFtUmVzcG9uc2USPQoOZW50aXR5X2NoYW5nZWQYAiABKAsyIy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFbnRpdHlDaGFuZ2VkSAASPQoOY29uZmlnX2NoYW5nZWQYAyABKAsyIy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRDb25maWdDaGFuZ2VkSAASRQoSZXhlY3V0aW9uX2ZpbmlzaGVkGAQgASgLMicub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uRmluaXNoZWRIABJDChFleGVjdXRpb25fc3RhcnRlZBgFIAEoCzImLm9saXZldGluLmFwaS52MS5FdmVudEV4ZWN1dGlvblN0YXJ0ZWRIABI5CgxvdXRwdXRfY2h1bmsYBiABKAsyIS5vbGl2ZXRpbi5hcGkudjEuRXZlbnRPdXRwdXRDaHVua0gAQgcKBWV2ZW50IkEKEEV2ZW50T3V0cHV0Q2h1bmsSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEg4KBm91dHB1dBgCIAEoCSIUChJFdmVudEVudGl0eUNoYW5nZWQiFAoSRXZlbnRDb25maWdDaGFuZ2VkIkYKFkV2ZW50RXhlY3V0aW9uRmluaXNoZWQSLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IkUKFUV2ZW50RXhlY3V0aW9uU3RhcnRlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiMgoRS2lsbEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJIm0KEktpbGxBY3Rpb25SZXNwb25zZRIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGa2lsbGVkGAIgASgIEhkKEWFscmVhZHlfY29tcGxldGVkGAMgASgIEg0KBWZvdW5kGAQgASgIIjsKFUxvY2FsVXNlckxvZ2luUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSIpChZMb2NhbFVzZXJMb2dpblJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgiJwoTUGFzc3dvcmRIYXNoUmVxdWVzdBIQCghwYXNzd29yZBgBIAEoCSIkChRQYXNzd29yZEhhc2hSZXNwb25zZRIMCgRoYXNoGAEgASgJIg8KDUxvZ291dFJlcXVlc3QiEAoOTG9nb3V0UmVzcG9uc2UiFwoVR2V0RGlhZ25vc3RpY3NSZXF1ZXN0IkUKFkdldERpYWdub3N0aWNzUmVzcG9uc2USEwoLU3NoRm91bmRLZXkYASABKAkSFgoOU3NoRm91bmRDb25maWcYAiABKAkiDQoLSW5pdFJlcXVlc3QiqwUKDEluaXRSZXNwb25zZRISCgpzaG93Rm9vdGVyGAEgASgIEhYKDnNob3dOYXZpZ2F0aW9uGAIgASgIEhcKD3Nob3dOZXdWZXJzaW9ucxgDIAEoCBIYChBhdmFpbGFibGVWZXJzaW9uGAQgASgJEhYKDmN1cnJlbnRWZXJzaW9uGAUgASgJEhEKCXBhZ2VUaXRsZRgGIAEoCRIeChZzZWN0aW9uTmF2aWdhdGlvblN0eWxlGAcgASgJEhoKEmRlZmF1bHRJY29uRm9yQmFjaxgIIAEoCRIWCg5lbmFibGVDdXN0b21KcxgJIAEoCBIUCgxhdXRoTG9naW5VcmwYCiABKAkSFgoOYXV0aExvY2FsTG9naW4YCyABKAgSEQoJc3R5bGVNb2RzGAwgAygJEjgKD29BdXRoMlByb3ZpZGVycxgNIAMoCzIfLm9saXZldGluLmFwaS52MS5PQXV0aDJQcm92aWRlchI4Cg9hZGRpdGlvbmFsTGlua3MYDiADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWRkaXRpb25hbExpbmsSFgoOcm9vdERhc2hib2FyZHMYDyADKAkSGgoSYXV0aGVudGljYXRlZF91c2VyGBAgASgJEiMKG2F1dGhlbnRpY2F0ZWRfdXNlcl9wcm92aWRlchgRIAEoCRI6ChBlZmZlY3RpdmVfcG9saWN5GBIgASgLMiAub2xpdmV0aW4uYXBpLnYxLkVmZmVjdGl2ZVBvbGljeRIWCg5iYW5uZXJfbWVzc2FnZRgTIAEoCRISCgpiYW5uZXJfY3NzGBQgASgJEhgKEHNob3dfZGlhZ25vc3RpY3MYFSABKAgSFQoNc2hvd19sb2dfbGlzdBgWIAEoCBIWCg5sb2dpbl9yZXF1aXJlZBgXIAEoCCIsCg5BZGRpdGlvbmFsTGluaxINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkiOgoOT0F1dGgyUHJvdmlkZXISDQoFdGl0bGUYASABKAkSDAoEaWNvbhgDIAEoCRILCgNrZXkYBCABKAkiLQoXR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCSJDChhHZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2USJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbiIUChJHZXRFbnRpdGllc1JlcXVlc3QiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiJpChBFbnRpdHlEZWZpbml0aW9uEg0KBXRpdGxlGAEgASgJEioKCWluc3RhbmNlcxgCIAMoCzIXLm9saXZldGluLmFwaS52MS5FbnRpdHkSGgoSdXNlZF9vbl9kYXNoYm9hcmRzGAMgAygJIjQKEEdldEVudGl0eVJlcXVlc3QSEgoKdW5pcXVlX2tleRgBIAEoCRIMCgR0eXBlGAIgASgJIjUKFFJlc3RhcnRBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCTLoEgoST2xpdmVUaW5BcGlTZXJ2aWNlEl0KDEdldERhc2hib2FyZBIkLm9saXZldGluLmFwaS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASWgoLU3RhcnRBY3Rpb24SIy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJvChJTdGFydEFjdGlvbkFuZFdhaXQSKi5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBorLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXNwb25zZSIAEmkKEFN0YXJ0QWN0aW9uQnlHZXQSKC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlIgASfgoXU3RhcnRBY3Rpb25CeUdldEFuZFdhaXQSLy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXF1ZXN0GjAub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2UiABJeCg1SZXN0YXJ0QWN0aW9uEiUub2xpdmV0aW4uYXBpLnYxLlJlc3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJXCgpLaWxsQWN0aW9uEiIub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXF1ZXN0GiMub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXNwb25zZSIAEmYKD0V4ZWN1dGlvblN0YXR1cxInLm9saXZldGluLmFwaS52MS5FeGVjdXRpb25TdGF0dXNSZXF1ZXN0Gigub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlIgASTgoHR2V0TG9ncxIfLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVxdWVzdBogLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVzcG9uc2UiABJgCg1HZXRBY3Rpb25Mb2dzEiUub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXF1ZXN0GiYub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXNwb25zZSIAEnUKFFZhbGlkYXRlQXJndW1lbnRUeXBlEiwub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBotLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlIgASSwoGV2hvQW1JEh4ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVzcG9uc2UiABJUCglTb3NSZXBvcnQSIS5vbGl2ZXRpbi5hcGkudjEuU29zUmVwb3J0UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXNwb25zZSIAElEKCER1bXBWYXJzEiAub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVxdWVzdBohLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlIgASeAoVRHVtcFB1YmxpY0lkQWN0aW9uTWFwEi0ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlcXVlc3QaLi5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UiABJUCglHZXRSZWFkeXoSIS5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXNwb25zZSIAEmMKDkxvY2FsVXNlckxvZ2luEiYub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlc3BvbnNlIgASXQoMUGFzc3dvcmRIYXNoEiQub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVzcG9uc2UiABJLCgZMb2dvdXQSHi5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVxdWVzdBofLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXNwb25zZSIAElwKC0V2ZW50U3RyZWFtEiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlc3BvbnNlIgAwARJjCg5HZXREaWFnbm9zdGljcxImLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1JlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXNwb25zZSIAEkUKBEluaXQSHC5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlcXVlc3QaHS5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlc3BvbnNlIgASaQoQR2V0QWN0aW9uQmluZGluZxIoLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBopLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2UiABJaCgtHZXRFbnRpdGllcxIjLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1JlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXNwb25zZSIAEkkKCUdldEVudGl0eRIhLm9saXZldGluLmFwaS52MS5HZXRFbnRpdHlSZXF1ZXN0Ghcub2xpdmV0aW4uYXBpLnYxLkVudGl0eSIAQjhaNmdpdGh1Yi5jb20vT2xpdmVUaW4vT2xpdmVUaW4vZ2VuL29saXZldGluL2FwaS92MTthcGl2MWIGcHJvdG8z"); /** * Describes the message olivetin.api.v1.Action. diff --git a/frontend/resources/vue/Dashboard.vue b/frontend/resources/vue/Dashboard.vue index 18e484b..16432bd 100644 --- a/frontend/resources/vue/Dashboard.vue +++ b/frontend/resources/vue/Dashboard.vue @@ -11,13 +11,39 @@ + + + + Back + + {{ dashboard.title }} This dashboard is empty. + + + + Back + + - {{ component.title }} + + + {{ component.title }} + + {{ component.title }} + @@ -31,23 +57,54 @@ diff --git a/proto/olivetin/api/v1/olivetin.proto b/proto/olivetin/api/v1/olivetin.proto index 78a1385..5f894d9 100644 --- a/proto/olivetin/api/v1/olivetin.proto +++ b/proto/olivetin/api/v1/olivetin.proto @@ -36,6 +36,7 @@ message Entity { string title = 1; string unique_key = 2; string type = 3; + repeated string directories = 4; } message GetDashboardResponse { @@ -51,6 +52,8 @@ message EffectivePolicy { message GetDashboardRequest { string title = 1; + string entity_type = 2; + string entity_key = 3; } message Dashboard { @@ -65,6 +68,8 @@ message DashboardComponent { string icon = 4; string css_class = 5; Action action = 6; + string entity_type = 7; + string entity_key = 8; } message StartActionRequest { diff --git a/service/gen/olivetin/api/v1/olivetin.pb.go b/service/gen/olivetin/api/v1/olivetin.pb.go index c2e5f05..1e31447 100644 --- a/service/gen/olivetin/api/v1/olivetin.pb.go +++ b/service/gen/olivetin/api/v1/olivetin.pb.go @@ -270,6 +270,7 @@ type Entity struct { Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` UniqueKey string `protobuf:"bytes,2,opt,name=unique_key,json=uniqueKey,proto3" json:"unique_key,omitempty"` Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Directories []string `protobuf:"bytes,4,rep,name=directories,proto3" json:"directories,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -325,6 +326,13 @@ func (x *Entity) GetType() string { return "" } +func (x *Entity) GetDirectories() []string { + if x != nil { + return x.Directories + } + return nil +} + type GetDashboardResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` @@ -432,6 +440,8 @@ func (x *EffectivePolicy) GetShowLogList() bool { type GetDashboardRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + EntityType string `protobuf:"bytes,2,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"` + EntityKey string `protobuf:"bytes,3,opt,name=entity_key,json=entityKey,proto3" json:"entity_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -473,6 +483,20 @@ func (x *GetDashboardRequest) GetTitle() string { return "" } +func (x *GetDashboardRequest) GetEntityType() string { + if x != nil { + return x.EntityType + } + return "" +} + +func (x *GetDashboardRequest) GetEntityKey() string { + if x != nil { + return x.EntityKey + } + return "" +} + type Dashboard struct { state protoimpl.MessageState `protogen:"open.v1"` Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` @@ -533,6 +557,8 @@ type DashboardComponent struct { Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` CssClass string `protobuf:"bytes,5,opt,name=css_class,json=cssClass,proto3" json:"css_class,omitempty"` Action *Action `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + EntityType string `protobuf:"bytes,7,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"` + EntityKey string `protobuf:"bytes,8,opt,name=entity_key,json=entityKey,proto3" json:"entity_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -609,6 +635,20 @@ func (x *DashboardComponent) GetAction() *Action { return nil } +func (x *DashboardComponent) GetEntityType() string { + if x != nil { + return x.EntityType + } + return "" +} + +func (x *DashboardComponent) GetEntityKey() string { + if x != nil { + return x.EntityKey + } + return "" +} + type StartActionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BindingId string `protobuf:"bytes,1,opt,name=binding_id,json=bindingId,proto3" json:"binding_id,omitempty"` @@ -3799,30 +3839,39 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"B\n" + "\x14ActionArgumentChoice\x12\x14\n" + "\x05value\x18\x01 \x01(\tR\x05value\x12\x14\n" + - "\x05title\x18\x02 \x01(\tR\x05title\"Q\n" + + "\x05title\x18\x02 \x01(\tR\x05title\"s\n" + "\x06Entity\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12\x1d\n" + "\n" + "unique_key\x18\x02 \x01(\tR\tuniqueKey\x12\x12\n" + - "\x04type\x18\x03 \x01(\tR\x04type\"f\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12 \n" + + "\vdirectories\x18\x04 \x03(\tR\vdirectories\"f\n" + "\x14GetDashboardResponse\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x128\n" + "\tdashboard\x18\x04 \x01(\v2\x1a.olivetin.api.v1.DashboardR\tdashboard\"`\n" + "\x0fEffectivePolicy\x12)\n" + "\x10show_diagnostics\x18\x01 \x01(\bR\x0fshowDiagnostics\x12\"\n" + - "\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\"+\n" + + "\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\"k\n" + "\x13GetDashboardRequest\x12\x14\n" + - "\x05title\x18\x01 \x01(\tR\x05title\"b\n" + + "\x05title\x18\x01 \x01(\tR\x05title\x12\x1f\n" + + "\ventity_type\x18\x02 \x01(\tR\n" + + "entityType\x12\x1d\n" + + "\n" + + "entity_key\x18\x03 \x01(\tR\tentityKey\"b\n" + "\tDashboard\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12?\n" + - "\bcontents\x18\x02 \x03(\v2#.olivetin.api.v1.DashboardComponentR\bcontents\"\xe1\x01\n" + + "\bcontents\x18\x02 \x03(\v2#.olivetin.api.v1.DashboardComponentR\bcontents\"\xa1\x02\n" + "\x12DashboardComponent\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12?\n" + "\bcontents\x18\x03 \x03(\v2#.olivetin.api.v1.DashboardComponentR\bcontents\x12\x12\n" + "\x04icon\x18\x04 \x01(\tR\x04icon\x12\x1b\n" + "\tcss_class\x18\x05 \x01(\tR\bcssClass\x12/\n" + - "\x06action\x18\x06 \x01(\v2\x17.olivetin.api.v1.ActionR\x06action\"\xa5\x01\n" + + "\x06action\x18\x06 \x01(\v2\x17.olivetin.api.v1.ActionR\x06action\x12\x1f\n" + + "\ventity_type\x18\a \x01(\tR\n" + + "entityType\x12\x1d\n" + + "\n" + + "entity_key\x18\b \x01(\tR\tentityKey\"\xa5\x01\n" + "\x12StartActionRequest\x12\x1d\n" + "\n" + "binding_id\x18\x01 \x01(\tR\tbindingId\x12B\n" + diff --git a/service/internal/api/api.go b/service/internal/api/api.go index f22c9d4..8c1501f 100644 --- a/service/internal/api/api.go +++ b/service/internal/api/api.go @@ -415,7 +415,13 @@ func (api *oliveTinAPI) GetDashboard(ctx ctx.Context, req *connect.Request[apiv1 return nil, err } - dashboardRenderRequest := api.createDashboardRenderRequest(user) + entityType := "" + entityKey := "" + if req.Msg != nil { + entityType = req.Msg.EntityType + entityKey = req.Msg.EntityKey + } + dashboardRenderRequest := api.createDashboardRenderRequest(user, entityType, entityKey) if api.isDefaultDashboard(req.Msg.Title) { return api.buildDefaultDashboardResponse(dashboardRenderRequest) @@ -431,11 +437,13 @@ func (api *oliveTinAPI) checkDashboardAccess(user *authpublic.AuthenticatedUser) return nil } -func (api *oliveTinAPI) createDashboardRenderRequest(user *authpublic.AuthenticatedUser) *DashboardRenderRequest { +func (api *oliveTinAPI) createDashboardRenderRequest(user *authpublic.AuthenticatedUser, entityType, entityKey string) *DashboardRenderRequest { return &DashboardRenderRequest{ AuthenticatedUser: user, cfg: api.cfg, ex: api.executor, + EntityType: entityType, + EntityKey: entityKey, } } @@ -835,7 +843,7 @@ func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitReq func (api *oliveTinAPI) buildRootDashboards(user *authpublic.AuthenticatedUser, dashboards []*config.DashboardComponent) []string { var rootDashboards []string - dashboardRenderRequest := api.createDashboardRenderRequest(user) + dashboardRenderRequest := api.createDashboardRenderRequest(user, "", "") api.addDefaultDashboardIfNeeded(&rootDashboards, dashboardRenderRequest) api.addCustomDashboards(&rootDashboards, dashboards, dashboardRenderRequest) @@ -987,6 +995,32 @@ func findEntityInComponents(entityTitle string, parentTitle string, components [ } } +func findDirectoriesInEntityFieldsets(entityType string, dashboards []*config.DashboardComponent) []string { + var directories []string + + for _, dashboard := range dashboards { + findDirectoriesInEntityFieldsetsRecursive(entityType, dashboard, &directories) + } + + return directories +} + +func findDirectoriesInEntityFieldsetsRecursive(entityType string, component *config.DashboardComponent, directories *[]string) { + if component.Entity == entityType { + for _, subitem := range component.Contents { + if subitem.Type == "directory" { + *directories = append(*directories, subitem.Title) + } + } + } + + if len(component.Contents) > 0 { + for _, subitem := range component.Contents { + findDirectoriesInEntityFieldsetsRecursive(entityType, subitem, directories) + } + } +} + func (api *oliveTinAPI) GetEntity(ctx ctx.Context, req *connect.Request[apiv1.GetEntityRequest]) (*connect.Response[apiv1.Entity], error) { user := auth.UserFromApiCall(ctx, req, api.cfg) @@ -1008,6 +1042,9 @@ func (api *oliveTinAPI) GetEntity(ctx ctx.Context, req *connect.Request[apiv1.Ge return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("entity with unique key %s not found in type %s", req.Msg.UniqueKey, req.Msg.Type)) } else { res.Title = entity.Title + res.UniqueKey = entity.UniqueKey + res.Type = req.Msg.Type + res.Directories = findDirectoriesInEntityFieldsets(req.Msg.Type, api.cfg.Dashboards) return connect.NewResponse(res), nil } diff --git a/service/internal/api/apiActions.go b/service/internal/api/apiActions.go index 473d2f8..d828efa 100644 --- a/service/internal/api/apiActions.go +++ b/service/internal/api/apiActions.go @@ -13,15 +13,31 @@ type DashboardRenderRequest struct { AuthenticatedUser *authpublic.AuthenticatedUser cfg *config.Config ex *executor.Executor + EntityType string + EntityKey string } func (rr *DashboardRenderRequest) findAction(title string) *apiv1.Action { + return rr.findActionForEntity(title, nil) +} + +func (rr *DashboardRenderRequest) findActionForEntity(title string, entity *entities.Entity) *apiv1.Action { rr.ex.MapActionIdToBindingLock.RLock() defer rr.ex.MapActionIdToBindingLock.RUnlock() for _, binding := range rr.ex.MapActionIdToBinding { - if binding.Action.Title == title { - return buildAction(binding, rr) + if binding.Action.Title != title { + continue + } + + if entity == nil { + if binding.Entity == nil { + return buildAction(binding, rr) + } + } else { + if binding.Entity != nil && binding.Entity.UniqueKey == entity.UniqueKey { + return buildAction(binding, rr) + } } } diff --git a/service/internal/api/dashboard_entities.go b/service/internal/api/dashboard_entities.go index 0202050..23db2e4 100644 --- a/service/internal/api/dashboard_entities.go +++ b/service/internal/api/dashboard_entities.go @@ -25,11 +25,13 @@ func buildEntityFieldsets(entityTitle string, tpl *config.DashboardComponent, rr func buildEntityFieldset(tpl *config.DashboardComponent, ent *entities.Entity, rr *DashboardRenderRequest) *apiv1.DashboardComponent { return &apiv1.DashboardComponent{ - Title: entities.ParseTemplateWith(tpl.Title, ent), - Type: "fieldset", - Contents: removeFieldsetIfHasNoLinks(buildEntityFieldsetContents(tpl.Contents, ent, rr)), - CssClass: entities.ParseTemplateWith(tpl.CssClass, ent), - Action: rr.findAction(tpl.Title), + Title: entities.ParseTemplateWith(tpl.Title, ent), + Type: "fieldset", + Contents: removeFieldsetIfHasNoLinks(buildEntityFieldsetContents(tpl.Contents, ent, tpl.Entity, rr)), + CssClass: entities.ParseTemplateWith(tpl.CssClass, ent), + Action: rr.findAction(tpl.Title), + EntityType: tpl.Entity, + EntityKey: ent.UniqueKey, } } @@ -48,11 +50,11 @@ func removeFieldsetIfHasNoLinks(contents []*apiv1.DashboardComponent) []*apiv1.D */ } -func buildEntityFieldsetContents(contents []*config.DashboardComponent, ent *entities.Entity, rr *DashboardRenderRequest) []*apiv1.DashboardComponent { +func buildEntityFieldsetContents(contents []*config.DashboardComponent, ent *entities.Entity, entityType string, rr *DashboardRenderRequest) []*apiv1.DashboardComponent { ret := make([]*apiv1.DashboardComponent, 0) for _, subitem := range contents { - c := cloneItem(subitem, ent, rr) + c := cloneItem(subitem, ent, entityType, rr) log.Infof("cloneItem: %+v", c) @@ -64,17 +66,26 @@ func buildEntityFieldsetContents(contents []*config.DashboardComponent, ent *ent return ret } -func cloneItem(subitem *config.DashboardComponent, ent *entities.Entity, rr *DashboardRenderRequest) *apiv1.DashboardComponent { +func cloneItem(subitem *config.DashboardComponent, ent *entities.Entity, entityType string, rr *DashboardRenderRequest) *apiv1.DashboardComponent { clone := &apiv1.DashboardComponent{} clone.CssClass = entities.ParseTemplateWith(subitem.CssClass, ent) if subitem.Type == "" || subitem.Type == "link" { clone.Type = "link" clone.Title = entities.ParseTemplateWith(subitem.Title, ent) - clone.Action = rr.findAction(subitem.Title) + clone.Action = rr.findActionForEntity(subitem.Title, ent) } else { clone.Title = entities.ParseTemplateWith(subitem.Title, ent) clone.Type = subitem.Type + + if clone.Type == "directory" && ent != nil && entityType != "" { + clone.EntityType = entityType + clone.EntityKey = ent.UniqueKey + } + + if len(subitem.Contents) > 0 { + clone.Contents = buildEntityFieldsetContents(subitem.Contents, ent, entityType, rr) + } } return clone diff --git a/service/internal/api/dashboards.go b/service/internal/api/dashboards.go index 389d553..4b62324 100644 --- a/service/internal/api/dashboards.go +++ b/service/internal/api/dashboards.go @@ -5,6 +5,7 @@ import ( apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1" config "github.com/OliveTin/OliveTin/internal/config" + entities "github.com/OliveTin/OliveTin/internal/entities" log "github.com/sirupsen/logrus" "golang.org/x/exp/slices" ) @@ -17,6 +18,19 @@ func renderDashboard(rr *DashboardRenderRequest, dashboardTitle string) *apiv1.D return findAndRenderDashboard(rr, dashboardTitle) } +func getEntityFromRequest(rr *DashboardRenderRequest) *entities.Entity { + if rr.EntityType == "" || rr.EntityKey == "" { + return nil + } + + entityInstances := entities.GetEntityInstances(rr.EntityType) + if entity, ok := entityInstances[rr.EntityKey]; ok { + return entity + } + + return nil +} + func findAndRenderDashboard(rr *DashboardRenderRequest, dashboardTitle string) *apiv1.Dashboard { for _, dashboard := range rr.cfg.Dashboards { if dashboard.Title != dashboardTitle { @@ -31,6 +45,35 @@ func findAndRenderDashboard(rr *DashboardRenderRequest, dashboardTitle string) * return buildDashboardFromConfig(dashboard, rr) } + directoryComponent := findDirectoryComponent(rr, dashboardTitle) + if directoryComponent != nil { + entity := getEntityFromRequest(rr) + return buildDashboardFromConfigWithEntity(directoryComponent, rr, entity) + } + + return nil +} + +func findDirectoryComponent(rr *DashboardRenderRequest, title string) *config.DashboardComponent { + for _, dashboard := range rr.cfg.Dashboards { + if component := searchDirectoryInComponent(dashboard, title); component != nil { + return component + } + } + return nil +} + +func searchDirectoryInComponent(component *config.DashboardComponent, title string) *config.DashboardComponent { + if component.Title == title && len(component.Contents) > 0 && component.Type != "fieldset" { + return component + } + + for _, subitem := range component.Contents { + if found := searchDirectoryInComponent(subitem, title); found != nil { + return found + } + } + return nil } @@ -42,9 +85,13 @@ func logEmptyDashboard(dashboardTitle, username string) { } func buildDashboardFromConfig(dashboard *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.Dashboard { + return buildDashboardFromConfigWithEntity(dashboard, rr, nil) +} + +func buildDashboardFromConfigWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.Dashboard { return &apiv1.Dashboard{ Title: dashboard.Title, - Contents: sortActions(removeNulls(getDashboardComponentContents(dashboard, rr))), + Contents: sortActions(removeNulls(getDashboardComponentContentsWithEntity(dashboard, rr, entity))), } } @@ -119,11 +166,15 @@ func removeNulls(components []*apiv1.DashboardComponent) []*apiv1.DashboardCompo } func getDashboardComponentContents(dashboard *config.DashboardComponent, rr *DashboardRenderRequest) []*apiv1.DashboardComponent { + return getDashboardComponentContentsWithEntity(dashboard, rr, nil) +} + +func getDashboardComponentContentsWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) []*apiv1.DashboardComponent { ret := make([]*apiv1.DashboardComponent, 0) rootFieldset := createRootFieldset() for _, subitem := range dashboard.Contents { - processDashboardSubitem(subitem, rr, &ret, rootFieldset) + processDashboardSubitemWithEntity(subitem, rr, &ret, rootFieldset, entity) } return appendRootFieldsetIfNeeded(ret, rootFieldset) @@ -138,15 +189,19 @@ func createRootFieldset() *apiv1.DashboardComponent { } func processDashboardSubitem(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent) { + processDashboardSubitemWithEntity(subitem, rr, ret, rootFieldset, nil) +} + +func processDashboardSubitemWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent, entity *entities.Entity) { if subitem.Type != "fieldset" { - rootFieldset.Contents = append(rootFieldset.Contents, buildDashboardComponentSimple(subitem, rr)) + rootFieldset.Contents = append(rootFieldset.Contents, buildDashboardComponentSimpleWithEntity(subitem, rr, entity)) return } if subitem.Entity != "" { *ret = append(*ret, buildEntityFieldsets(subitem.Entity, subitem, rr)...) } else { - *ret = append(*ret, buildDashboardComponentSimple(subitem, rr)) + *ret = append(*ret, buildDashboardComponentSimpleWithEntity(subitem, rr, entity)) } } @@ -158,13 +213,31 @@ func appendRootFieldsetIfNeeded(ret []*apiv1.DashboardComponent, rootFieldset *a } func buildDashboardComponentSimple(subitem *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent { + return buildDashboardComponentSimpleWithEntity(subitem, rr, nil) +} + +func buildDashboardComponentSimpleWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.DashboardComponent { + var contents []*apiv1.DashboardComponent + + if len(subitem.Contents) > 0 { + contents = getDashboardComponentContentsWithEntity(subitem, rr, entity) + } + + action := rr.findActionForEntity(subitem.Title, entity) + componentType := getDashboardComponentType(subitem, action) + + title := subitem.Title + if entity != nil { + title = entities.ParseTemplateWith(subitem.Title, entity) + } + newitem := &apiv1.DashboardComponent{ - Title: subitem.Title, - Type: getDashboardComponentType(subitem), - Contents: getDashboardComponentContents(subitem, rr), + Title: title, + Type: componentType, + Contents: contents, Icon: getDashboardComponentIcon(subitem, rr.cfg), CssClass: subitem.CssClass, - Action: rr.findAction(subitem.Title), + Action: action, } return newitem @@ -178,7 +251,7 @@ func getDashboardComponentIcon(item *config.DashboardComponent, cfg *config.Conf return item.Icon } -func getDashboardComponentType(item *config.DashboardComponent) string { +func getDashboardComponentType(item *config.DashboardComponent, action *apiv1.Action) string { allowedTypes := []string{ "stdout-most-recent-execution", "display", @@ -194,5 +267,9 @@ func getDashboardComponentType(item *config.DashboardComponent) string { return item.Type } + if action == nil { + return "display" + } + return "link" }
This dashboard is empty.