Merge branch 'next' into dependabot/go_modules/service/next/github.com/bufbuild/buf-1.61.0
This commit is contained in:
commit
75a696d8c0
|
|
@ -151,6 +151,11 @@ export declare type Entity = Message<"olivetin.api.v1.Entity"> & {
|
|||
* @generated from field: repeated string directories = 4;
|
||||
*/
|
||||
directories: string[];
|
||||
|
||||
/**
|
||||
* @generated from field: map<string, string> fields = 5;
|
||||
*/
|
||||
fields: { [key: string]: string };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -21,10 +21,14 @@
|
|||
|
||||
<h3>Used on Dashboards:</h3>
|
||||
<ul>
|
||||
<li v-for="dash in def.usedOnDashboards">
|
||||
<router-link :to="{ name: 'Dashboard', params: { title: dash } }">
|
||||
{{ dash }}
|
||||
<li v-for="dash in filteredDashboards(def.usedOnDashboards)" :key="dash">
|
||||
<template v-if="isEntityDirectory(dash)">
|
||||
{{ getDashboardTitle(dash) }} <span class="entity-directory-label">[Entity Directory]</span>
|
||||
</template>
|
||||
<router-link v-else-if="!dash.includes('entity:')" :to="{ name: 'Dashboard', params: { title: getDashboardTitle(dash) } }">
|
||||
{{ getDashboardTitle(dash) }}
|
||||
</router-link>
|
||||
<span v-else>{{ dash }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -44,6 +48,21 @@
|
|||
entityDefinitions.value = ret.entityDefinitions
|
||||
}
|
||||
|
||||
function filteredDashboards(dashboards) {
|
||||
return dashboards.filter(d => d && !d.includes('{{'))
|
||||
}
|
||||
|
||||
function isEntityDirectory(dashboardTitle) {
|
||||
return dashboardTitle.endsWith(' [Entity Directory]')
|
||||
}
|
||||
|
||||
function getDashboardTitle(dashboardTitle) {
|
||||
if (isEntityDirectory(dashboardTitle)) {
|
||||
return dashboardTitle.slice(0, -' [Entity Directory]'.length)
|
||||
}
|
||||
return dashboardTitle
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchEntities()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,15 +19,21 @@
|
|||
</dd>
|
||||
<dt v-if="entityDetails.title">Title</dt>
|
||||
<dd v-if="entityDetails.title">{{ entityDetails.title }}</dd>
|
||||
<template v-if="entityDetails.fields">
|
||||
<template v-for="(value, key) in entityDetails.fields" :key="key">
|
||||
<dt>{{ key }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
</template>
|
||||
</template>
|
||||
</dl>
|
||||
<p v-if="!entityDetails.title">No details available for this entity.</p>
|
||||
<p v-if="!entityDetails.title && (!entityDetails.fields || Object.keys(entityDetails.fields).length === 0)">No details available for this entity.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3>Dashboard Entity Directories</h3>
|
||||
<div v-if="entityDetails.directories && entityDetails.directories.length > 0" class="directories-section">
|
||||
<div v-if="filteredDirectories.length > 0" class="directories-section">
|
||||
<ul class="directory-list">
|
||||
<li v-for="directory in entityDetails.directories" :key="directory">
|
||||
<li v-for="(directory, idx) in filteredDirectories" :key="idx">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Dashboard',
|
||||
|
|
@ -50,7 +56,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
|
|
@ -64,6 +70,13 @@
|
|||
entityKey: String
|
||||
})
|
||||
|
||||
const filteredDirectories = computed(() => {
|
||||
if (!entityDetails.value?.directories) {
|
||||
return []
|
||||
}
|
||||
return entityDetails.value.directories.filter(d => d)
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'Entities' })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,10 @@ import fs from 'fs'
|
|||
import { expect } from 'chai'
|
||||
import { Condition } from 'selenium-webdriver'
|
||||
|
||||
export async function getActionButtons (dashboardTitle = null) {
|
||||
// New Vue UI renders action buttons using ActionButton.vue structure
|
||||
// Each button lives under a container with class .action-button
|
||||
if (dashboardTitle == null) {
|
||||
return await webdriver.findElements(By.css('.action-button button'))
|
||||
} else {
|
||||
return await webdriver.findElements(By.css('section[title="' + dashboardTitle + '"] .action-button button'))
|
||||
}
|
||||
export async function getActionButtons () {
|
||||
// Currently, only the active dashboard's contents are rendered,
|
||||
// so we don't need to scope the selector by dashboard title.
|
||||
return await webdriver.findElements(By.css('.action-button button'))
|
||||
}
|
||||
|
||||
export async function getExecutionDialogOutput() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Integration Test Config: inline dashboard actions
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
# No top-level actions – actions will be defined inline on dashboard components.
|
||||
actions: []
|
||||
|
||||
dashboards:
|
||||
- title: Inline Dashboard
|
||||
contents:
|
||||
- title: Inline Dashboard Action
|
||||
inlineAction:
|
||||
shell: date
|
||||
icon: clock
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { describe, it, before, after } from 'mocha'
|
||||
import { assert } from 'chai'
|
||||
import {
|
||||
getRootAndWait,
|
||||
getActionButtons,
|
||||
takeScreenshotOnFailure,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: inlineActions', function () {
|
||||
before(async function () {
|
||||
await runner.start('inlineActions')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
takeScreenshotOnFailure(this.currentTest, webdriver);
|
||||
});
|
||||
|
||||
it('Inline dashboard actions are rendered as clickable buttons', async function () {
|
||||
await getRootAndWait()
|
||||
|
||||
const buttons = await getActionButtons()
|
||||
assert.isArray(buttons, 'Action buttons should be an array')
|
||||
assert.isAtLeast(buttons.length, 1, 'There should be at least one action button')
|
||||
|
||||
const texts = await Promise.all(buttons.map(b => b.getText()))
|
||||
const combinedText = texts.join(' ')
|
||||
|
||||
assert.include(
|
||||
combinedText,
|
||||
'Inline Dashboard Action',
|
||||
'Inline dashboard action should be rendered as a button'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
include: dashboards.d
|
||||
|
||||
actions:
|
||||
- title: Base Action
|
||||
shell: echo "base"
|
||||
icon: ping
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
dashboards:
|
||||
- title: First Dashboard
|
||||
contents:
|
||||
- type: action
|
||||
inlineAction:
|
||||
title: First Action
|
||||
shell: echo "First Dashboard, First Action!"
|
||||
|
||||
- type: action
|
||||
inlineAction:
|
||||
title: Second Action
|
||||
shell: echo "First Dashboard, Second Action!"
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
entities:
|
||||
- file: entities/person.yaml
|
||||
name: person
|
||||
|
||||
dashboards:
|
||||
- title: Second Dashboard
|
||||
contents:
|
||||
- type: action
|
||||
inlineAction:
|
||||
title: Third Action
|
||||
shell: echo "Second Dashboard, Third Action!"
|
||||
|
||||
- type: action
|
||||
inlineAction:
|
||||
title: Fourth Action
|
||||
shell: echo "Second Dashboard, Fourth Action!"
|
||||
|
||||
- title: "Person: {{ person.name }}"
|
||||
type: fieldset
|
||||
entity: person
|
||||
contents:
|
||||
- type: display
|
||||
title: " {{ person.name }} is a person"
|
||||
cssStyle:
|
||||
background-color: red;
|
||||
|
||||
- title: "Greet {{ person.name }}"
|
||||
inlineAction:
|
||||
shell: echo "Hello, {{ person.name }}!"
|
||||
icon: ping
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
- name: Alice
|
||||
- name: Bob
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
import { describe, it, before, after } from 'mocha'
|
||||
import { expect, assert } from 'chai'
|
||||
import { By } from 'selenium-webdriver'
|
||||
import {
|
||||
getRootAndWait,
|
||||
getActionButtons,
|
||||
getNavigationLinks,
|
||||
openSidebar,
|
||||
takeScreenshotOnFailure,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: multi-dashboard-includes', function () {
|
||||
this.timeout(30000)
|
||||
|
||||
before(async function () {
|
||||
await runner.start('multi-dashboard-includes')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
takeScreenshotOnFailure(this.currentTest, webdriver);
|
||||
});
|
||||
|
||||
async function clickNavigationLinkByTitle (title) {
|
||||
await openSidebar()
|
||||
|
||||
const navigationLinks = await getNavigationLinks()
|
||||
assert.isAbove(navigationLinks.length, 0, 'Expected at least one navigation link')
|
||||
|
||||
const matching = []
|
||||
for (const li of navigationLinks) {
|
||||
const liTitle = await li.getAttribute('title')
|
||||
if (liTitle === title) {
|
||||
matching.push(li)
|
||||
}
|
||||
}
|
||||
|
||||
assert.strictEqual(matching.length, 1, `Expected exactly one navigation link with title "${title}"`)
|
||||
|
||||
await matching[0].click()
|
||||
}
|
||||
|
||||
async function getActionTitlesOnDashboard (dashboardTitle = null) {
|
||||
const buttons = await getActionButtons(dashboardTitle)
|
||||
const titles = []
|
||||
|
||||
for (const button of buttons) {
|
||||
titles.push(await button.getAttribute('title'))
|
||||
}
|
||||
|
||||
return titles
|
||||
}
|
||||
|
||||
it('Should expose both dashboards from included files in navigation', async function () {
|
||||
await getRootAndWait()
|
||||
|
||||
await openSidebar()
|
||||
const navigationLinks = await getNavigationLinks()
|
||||
assert.isAbove(navigationLinks.length, 0, 'Expected navigation to have at least one link')
|
||||
|
||||
const titles = []
|
||||
for (const li of navigationLinks) {
|
||||
titles.push(await li.getAttribute('title'))
|
||||
}
|
||||
|
||||
expect(titles).to.include('First Dashboard')
|
||||
expect(titles).to.include('Second Dashboard')
|
||||
})
|
||||
|
||||
it('First Dashboard shows First and Second inline actions from include', async function () {
|
||||
await getRootAndWait()
|
||||
|
||||
// Navigate to "First Dashboard"
|
||||
await clickNavigationLinkByTitle('First Dashboard')
|
||||
|
||||
// Buttons on this dashboard only
|
||||
const titles = await getActionTitlesOnDashboard('First Dashboard')
|
||||
|
||||
expect(titles).to.include('First Action')
|
||||
expect(titles).to.include('Second Action')
|
||||
|
||||
// Ensure actions from the second dashboard are not rendered here
|
||||
expect(titles).to.not.include('Third Action')
|
||||
expect(titles).to.not.include('Fourth Action')
|
||||
})
|
||||
|
||||
it('Second Dashboard shows Third and Fourth inline actions from include', async function () {
|
||||
await getRootAndWait()
|
||||
|
||||
// Navigate to "Second Dashboard"
|
||||
await clickNavigationLinkByTitle('Second Dashboard')
|
||||
|
||||
const titles = await getActionTitlesOnDashboard('Second Dashboard')
|
||||
|
||||
expect(titles).to.include('Third Action')
|
||||
expect(titles).to.include('Fourth Action')
|
||||
|
||||
// Ensure actions from the first dashboard are not rendered here
|
||||
expect(titles).to.not.include('First Action')
|
||||
expect(titles).to.not.include('Second Action')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ message Entity {
|
|||
string unique_key = 2;
|
||||
string type = 3;
|
||||
repeated string directories = 4;
|
||||
map<string, string> fields = 5;
|
||||
}
|
||||
|
||||
message GetDashboardResponse {
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@ type Entity struct {
|
|||
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"`
|
||||
Fields map[string]string `protobuf:"bytes,5,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -333,6 +334,13 @@ func (x *Entity) GetDirectories() []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *Entity) GetFields() map[string]string {
|
||||
if x != nil {
|
||||
return x.Fields
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetDashboardResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
||||
|
|
@ -3839,13 +3847,17 @@ 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\"s\n" +
|
||||
"\x05title\x18\x02 \x01(\tR\x05title\"\xeb\x01\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\x12 \n" +
|
||||
"\vdirectories\x18\x04 \x03(\tR\vdirectories\"f\n" +
|
||||
"\vdirectories\x18\x04 \x03(\tR\vdirectories\x12;\n" +
|
||||
"\x06fields\x18\x05 \x03(\v2#.olivetin.api.v1.Entity.FieldsEntryR\x06fields\x1a9\n" +
|
||||
"\vFieldsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"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" +
|
||||
|
|
@ -4113,7 +4125,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, 71)
|
||||
var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 72)
|
||||
var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
||||
(*Action)(nil), // 0: olivetin.api.v1.Action
|
||||
(*ActionArgument)(nil), // 1: olivetin.api.v1.ActionArgument
|
||||
|
|
@ -4184,95 +4196,97 @@ var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
|||
(*GetEntityRequest)(nil), // 66: olivetin.api.v1.GetEntityRequest
|
||||
(*RestartActionRequest)(nil), // 67: olivetin.api.v1.RestartActionRequest
|
||||
nil, // 68: olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
nil, // 69: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
nil, // 70: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
nil, // 69: olivetin.api.v1.Entity.FieldsEntry
|
||||
nil, // 70: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
nil, // 71: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
}
|
||||
var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{
|
||||
1, // 0: olivetin.api.v1.Action.arguments:type_name -> olivetin.api.v1.ActionArgument
|
||||
2, // 1: olivetin.api.v1.ActionArgument.choices:type_name -> olivetin.api.v1.ActionArgumentChoice
|
||||
68, // 2: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
7, // 3: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard
|
||||
8, // 4: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
8, // 5: olivetin.api.v1.DashboardComponent.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
0, // 6: olivetin.api.v1.DashboardComponent.action:type_name -> olivetin.api.v1.Action
|
||||
10, // 7: olivetin.api.v1.StartActionRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||
10, // 8: olivetin.api.v1.StartActionAndWaitRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||
19, // 9: olivetin.api.v1.StartActionAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 10: olivetin.api.v1.StartActionByGetAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 11: olivetin.api.v1.GetLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 12: olivetin.api.v1.GetActionLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 13: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
69, // 14: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
70, // 15: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
43, // 16: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged
|
||||
44, // 17: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged
|
||||
45, // 18: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
||||
46, // 19: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
||||
42, // 20: olivetin.api.v1.EventStreamResponse.output_chunk:type_name -> olivetin.api.v1.EventOutputChunk
|
||||
19, // 21: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 22: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
60, // 23: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
||||
59, // 24: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
||||
5, // 25: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
||||
0, // 26: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
||||
65, // 27: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
||||
3, // 28: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
||||
35, // 29: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.ActionEntityPair
|
||||
6, // 30: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
||||
9, // 31: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
||||
12, // 32: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
||||
14, // 33: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
||||
16, // 34: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
||||
67, // 35: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
||||
47, // 36: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
||||
27, // 37: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
||||
18, // 38: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
||||
21, // 39: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
||||
23, // 40: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
||||
29, // 41: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
||||
31, // 42: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
||||
33, // 43: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
||||
36, // 44: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
||||
38, // 45: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
||||
49, // 46: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
||||
51, // 47: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
||||
53, // 48: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
||||
40, // 49: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
||||
55, // 50: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
||||
57, // 51: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
||||
61, // 52: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
||||
63, // 53: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
||||
66, // 54: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
||||
4, // 55: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
||||
11, // 56: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
13, // 57: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
||||
15, // 58: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
||||
17, // 59: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
||||
11, // 60: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
48, // 61: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
||||
28, // 62: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
||||
20, // 63: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
||||
22, // 64: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
||||
24, // 65: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
||||
30, // 66: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
||||
32, // 67: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
||||
34, // 68: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
||||
37, // 69: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
||||
39, // 70: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
||||
50, // 71: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
||||
52, // 72: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
||||
54, // 73: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
||||
41, // 74: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
||||
56, // 75: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
||||
58, // 76: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
||||
62, // 77: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
||||
64, // 78: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
||||
3, // 79: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
||||
55, // [55:80] is the sub-list for method output_type
|
||||
30, // [30:55] is the sub-list for method input_type
|
||||
30, // [30:30] is the sub-list for extension type_name
|
||||
30, // [30:30] is the sub-list for extension extendee
|
||||
0, // [0:30] is the sub-list for field type_name
|
||||
69, // 3: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry
|
||||
7, // 4: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard
|
||||
8, // 5: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
8, // 6: olivetin.api.v1.DashboardComponent.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
0, // 7: olivetin.api.v1.DashboardComponent.action:type_name -> olivetin.api.v1.Action
|
||||
10, // 8: olivetin.api.v1.StartActionRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||
10, // 9: olivetin.api.v1.StartActionAndWaitRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||
19, // 10: olivetin.api.v1.StartActionAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 11: olivetin.api.v1.StartActionByGetAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 12: olivetin.api.v1.GetLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 13: olivetin.api.v1.GetActionLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 14: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
70, // 15: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
71, // 16: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
43, // 17: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged
|
||||
44, // 18: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged
|
||||
45, // 19: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
||||
46, // 20: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
||||
42, // 21: olivetin.api.v1.EventStreamResponse.output_chunk:type_name -> olivetin.api.v1.EventOutputChunk
|
||||
19, // 22: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
19, // 23: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
60, // 24: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
||||
59, // 25: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
||||
5, // 26: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
||||
0, // 27: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
||||
65, // 28: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
||||
3, // 29: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
||||
35, // 30: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.ActionEntityPair
|
||||
6, // 31: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
||||
9, // 32: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
||||
12, // 33: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
||||
14, // 34: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
||||
16, // 35: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
||||
67, // 36: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
||||
47, // 37: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
||||
27, // 38: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
||||
18, // 39: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
||||
21, // 40: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
||||
23, // 41: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
||||
29, // 42: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
||||
31, // 43: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
||||
33, // 44: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
||||
36, // 45: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
||||
38, // 46: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
||||
49, // 47: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
||||
51, // 48: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
||||
53, // 49: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
||||
40, // 50: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
||||
55, // 51: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
||||
57, // 52: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
||||
61, // 53: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
||||
63, // 54: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
||||
66, // 55: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
||||
4, // 56: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
||||
11, // 57: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
13, // 58: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
||||
15, // 59: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
||||
17, // 60: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
||||
11, // 61: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
48, // 62: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
||||
28, // 63: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
||||
20, // 64: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
||||
22, // 65: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
||||
24, // 66: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
||||
30, // 67: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
||||
32, // 68: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
||||
34, // 69: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
||||
37, // 70: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
||||
39, // 71: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
||||
50, // 72: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
||||
52, // 73: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
||||
54, // 74: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
||||
41, // 75: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
||||
56, // 76: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
||||
58, // 77: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
||||
62, // 78: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
||||
64, // 79: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
||||
3, // 80: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
||||
56, // [56:81] is the sub-list for method output_type
|
||||
31, // [31:56] is the sub-list for method input_type
|
||||
31, // [31:31] is the sub-list for extension type_name
|
||||
31, // [31:31] is the sub-list for extension extendee
|
||||
0, // [0:31] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_olivetin_api_v1_olivetin_proto_init() }
|
||||
|
|
@ -4293,7 +4307,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: 71,
|
||||
NumMessages: 72,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -977,24 +977,49 @@ func buildSortedEntityInstances(entityType string, entityInstances map[string]*e
|
|||
|
||||
func findDashboardsForEntity(entityTitle string, dashboards []*config.DashboardComponent) []string {
|
||||
var foundDashboards []string
|
||||
seen := make(map[string]bool)
|
||||
|
||||
findEntityInComponents(entityTitle, "", dashboards, &foundDashboards)
|
||||
findEntityInComponents(entityTitle, "", dashboards, &foundDashboards, seen)
|
||||
|
||||
return foundDashboards
|
||||
}
|
||||
|
||||
func findEntityInComponents(entityTitle string, parentTitle string, components []*config.DashboardComponent, foundDashboards *[]string) {
|
||||
func findEntityInComponents(entityTitle string, parentTitle string, components []*config.DashboardComponent, foundDashboards *[]string, seen map[string]bool) {
|
||||
for _, component := range components {
|
||||
if component.Entity == entityTitle {
|
||||
*foundDashboards = append(*foundDashboards, parentTitle)
|
||||
addEntityDashboard(component, parentTitle, foundDashboards, seen)
|
||||
}
|
||||
|
||||
if len(component.Contents) > 0 {
|
||||
findEntityInComponents(entityTitle, component.Title, component.Contents, foundDashboards)
|
||||
findEntityInComponents(entityTitle, component.Title, component.Contents, foundDashboards, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addEntityDashboard(component *config.DashboardComponent, parentTitle string, foundDashboards *[]string, seen map[string]bool) {
|
||||
if component.Type == "directory" {
|
||||
addEntityDirectory(component, foundDashboards, seen)
|
||||
} else {
|
||||
addParentDashboard(parentTitle, foundDashboards, seen)
|
||||
}
|
||||
}
|
||||
|
||||
func addEntityDirectory(component *config.DashboardComponent, foundDashboards *[]string, seen map[string]bool) {
|
||||
dashboardTitle := component.Title + " [Entity Directory]"
|
||||
if !seen[dashboardTitle] {
|
||||
*foundDashboards = append(*foundDashboards, dashboardTitle)
|
||||
seen[dashboardTitle] = true
|
||||
seen[component.Title] = true
|
||||
}
|
||||
}
|
||||
|
||||
func addParentDashboard(parentTitle string, foundDashboards *[]string, seen map[string]bool) {
|
||||
if parentTitle != "" && !seen[parentTitle] {
|
||||
*foundDashboards = append(*foundDashboards, parentTitle)
|
||||
seen[parentTitle] = true
|
||||
}
|
||||
}
|
||||
|
||||
func findDirectoriesInEntityFieldsets(entityType string, dashboards []*config.DashboardComponent) []string {
|
||||
var directories []string
|
||||
|
||||
|
|
@ -1036,26 +1061,46 @@ func (api *oliveTinAPI) GetEntity(ctx ctx.Context, req *connect.Request[apiv1.Ge
|
|||
return nil, err
|
||||
}
|
||||
|
||||
res := &apiv1.Entity{}
|
||||
|
||||
instances := entities.GetEntityInstances(req.Msg.Type)
|
||||
|
||||
log.Infof("msg: %+v", req.Msg)
|
||||
|
||||
if len(instances) == 0 {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("entity type %s not found", req.Msg.Type))
|
||||
}
|
||||
|
||||
if entity, ok := instances[req.Msg.UniqueKey]; !ok {
|
||||
entity, ok := instances[req.Msg.UniqueKey]
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
res := buildEntityResponse(entity, req.Msg.Type, api.cfg.Dashboards)
|
||||
return connect.NewResponse(res), nil
|
||||
}
|
||||
|
||||
func buildEntityResponse(entity *entities.Entity, entityType string, dashboards []*config.DashboardComponent) *apiv1.Entity {
|
||||
res := &apiv1.Entity{
|
||||
Title: entity.Title,
|
||||
UniqueKey: entity.UniqueKey,
|
||||
Type: entityType,
|
||||
Directories: findDirectoriesInEntityFieldsets(entityType, dashboards),
|
||||
Fields: serializeEntityFields(entity.Data),
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func serializeEntityFields(data any) map[string]string {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dataMap, ok := data.(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := make(map[string]string)
|
||||
for k, v := range dataMap {
|
||||
fields[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv1.RestartActionRequest]) (*connect.Response[apiv1.StartActionResponse], error) {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,16 @@ func isLinkType(itemType string) bool {
|
|||
func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clone *apiv1.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
||||
clone.Type = "link"
|
||||
clone.Title = entities.ParseTemplateWith(subitem.Title, ent)
|
||||
clone.Action = rr.findActionForEntity(subitem.Title, ent)
|
||||
// Prefer an entity-specific action when available, but fall back to a
|
||||
// non-entity-scoped action with the same title. This allows inline actions
|
||||
// defined inside entity dashboards to work without requiring an explicit
|
||||
// entity binding.
|
||||
action := rr.findActionForEntity(subitem.Title, ent)
|
||||
if action == nil {
|
||||
action = rr.findAction(subitem.Title)
|
||||
}
|
||||
|
||||
clone.Action = action
|
||||
return clone
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,10 +185,6 @@ func removeNulls(components []*apiv1.DashboardComponent) []*apiv1.DashboardCompo
|
|||
return ret
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
@ -208,10 +204,6 @@ 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, buildDashboardComponentSimpleWithEntity(subitem, rr, entity))
|
||||
|
|
@ -232,10 +224,6 @@ func appendRootFieldsetIfNeeded(ret []*apiv1.DashboardComponent, rootFieldset *a
|
|||
return ret
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -207,12 +207,13 @@ type LogDebugOptions struct {
|
|||
}
|
||||
|
||||
type DashboardComponent struct {
|
||||
Title string `koanf:"title"`
|
||||
Type string `koanf:"type"`
|
||||
Entity string `koanf:"entity"`
|
||||
Icon string `koanf:"icon"`
|
||||
CssClass string `koanf:"cssClass"`
|
||||
Contents []*DashboardComponent `koanf:"contents"`
|
||||
Title string `koanf:"title"`
|
||||
Type string `koanf:"type"`
|
||||
Entity string `koanf:"entity"`
|
||||
Icon string `koanf:"icon"`
|
||||
CssClass string `koanf:"cssClass"`
|
||||
InlineAction *Action `koanf:"inlineAction"`
|
||||
Contents []*DashboardComponent `koanf:"contents"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
|
|
|
|||
|
|
@ -192,10 +192,59 @@ func mergeActionsFromSource(srcActions interface{}, dest map[string]interface{})
|
|||
}
|
||||
}
|
||||
|
||||
// mergeDashboardsWhenBothExist merges dashboards when both src and dest have dashboards.
|
||||
func mergeDashboardsWhenBothExist(srcDashboards interface{}, destDashboards interface{}, dest map[string]interface{}) {
|
||||
srcSlice, ok1 := srcDashboards.([]interface{})
|
||||
destSlice, ok2 := destDashboards.([]interface{})
|
||||
if ok1 && ok2 {
|
||||
dest["dashboards"] = append(destSlice, srcSlice...)
|
||||
} else {
|
||||
dest["dashboards"] = srcDashboards
|
||||
}
|
||||
}
|
||||
|
||||
// mergeDashboardsFromSource merges dashboards from source into destination.
|
||||
func mergeDashboardsFromSource(srcDashboards interface{}, dest map[string]interface{}) {
|
||||
if destDashboards, ok := dest["dashboards"]; ok {
|
||||
mergeDashboardsWhenBothExist(srcDashboards, destDashboards, dest)
|
||||
} else {
|
||||
dest["dashboards"] = srcDashboards
|
||||
}
|
||||
}
|
||||
|
||||
// mergeEntitiesWhenBothExist merges entities when both src and dest have entities.
|
||||
func mergeEntitiesWhenBothExist(srcEntities interface{}, destEntities interface{}, dest map[string]interface{}) {
|
||||
srcSlice, ok1 := srcEntities.([]interface{})
|
||||
destSlice, ok2 := destEntities.([]interface{})
|
||||
if ok1 && ok2 {
|
||||
dest["entities"] = append(destSlice, srcSlice...)
|
||||
} else {
|
||||
dest["entities"] = srcEntities
|
||||
}
|
||||
}
|
||||
|
||||
// mergeEntitiesFromSource merges entities from source into destination.
|
||||
func mergeEntitiesFromSource(srcEntities interface{}, dest map[string]interface{}) {
|
||||
if destEntities, ok := dest["entities"]; ok {
|
||||
mergeEntitiesWhenBothExist(srcEntities, destEntities, dest)
|
||||
} else {
|
||||
dest["entities"] = srcEntities
|
||||
}
|
||||
}
|
||||
|
||||
func mergeFunc(src map[string]interface{}, dest map[string]interface{}) error {
|
||||
if srcActions, ok := src["actions"]; ok {
|
||||
mergeActionsFromSource(srcActions, dest)
|
||||
}
|
||||
|
||||
if srcDashboards, ok := src["dashboards"]; ok {
|
||||
mergeDashboardsFromSource(srcDashboards, dest)
|
||||
}
|
||||
|
||||
if srcEntities, ok := src["entities"]; ok {
|
||||
mergeEntitiesFromSource(srcEntities, dest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,113 @@ func (cfg *Config) Sanitize() {
|
|||
for idx := range cfg.Actions {
|
||||
cfg.Actions[idx].sanitize(cfg)
|
||||
}
|
||||
|
||||
cfg.sanitizeDashboardsForInlineActions()
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeDashboardsForInlineActions() {
|
||||
for _, dashboard := range cfg.Dashboards {
|
||||
cfg.sanitizeDashboardComponentForInlineActions(dashboard)
|
||||
}
|
||||
}
|
||||
func (cfg *Config) sanitizeDashboardComponentForInlineActions(component *DashboardComponent) {
|
||||
visited := make(map[*DashboardComponent]bool)
|
||||
cfg.sanitizeDashboardComponentForInlineActionsHelper(component, visited)
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeDashboardComponentForInlineActionsHelper(component *DashboardComponent, visited map[*DashboardComponent]bool) {
|
||||
if component == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if visited[component] {
|
||||
return
|
||||
}
|
||||
|
||||
visited[component] = true
|
||||
|
||||
cfg.sanitizeInlineAction(component)
|
||||
cfg.sanitizeChildDashboardComponents(component, visited)
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeInlineAction(component *DashboardComponent) {
|
||||
if component.InlineAction == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sanitizeInlineActionTitles(component)
|
||||
|
||||
if component.Entity != "" && component.InlineAction.Entity == "" {
|
||||
component.InlineAction.Entity = component.Entity
|
||||
}
|
||||
|
||||
component.InlineAction.sanitize(cfg)
|
||||
|
||||
cfg.addInlineActionIfNotExists(component.InlineAction)
|
||||
}
|
||||
|
||||
func (cfg *Config) addInlineActionIfNotExists(action *Action) {
|
||||
if cfg.inlineActionExists(action) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Actions = append(cfg.Actions, action)
|
||||
}
|
||||
|
||||
func sanitizeInlineActionTitles(component *DashboardComponent) {
|
||||
if component.InlineAction.Title == "" {
|
||||
component.InlineAction.Title = component.Title
|
||||
}
|
||||
|
||||
if component.Title == "" {
|
||||
component.Title = component.InlineAction.Title
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) inlineActionExists(action *Action) bool {
|
||||
if cfg.inlineActionPointerExists(action) {
|
||||
return true
|
||||
}
|
||||
|
||||
if cfg.inlineActionIDExists(action) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cfg *Config) inlineActionPointerExists(action *Action) bool {
|
||||
for _, existingAction := range cfg.Actions {
|
||||
if existingAction == action {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cfg *Config) inlineActionIDExists(action *Action) bool {
|
||||
if action.ID == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, existingAction := range cfg.Actions {
|
||||
if existingAction.ID == action.ID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeChildDashboardComponents(component *DashboardComponent, visited map[*DashboardComponent]bool) {
|
||||
for _, child := range component.Contents {
|
||||
if child.Entity == "" {
|
||||
child.Entity = component.Entity
|
||||
}
|
||||
|
||||
cfg.sanitizeDashboardComponentForInlineActionsHelper(child, visited)
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeLogLevel() {
|
||||
|
|
|
|||
|
|
@ -36,3 +36,39 @@ func TestSanitizeConfig(t *testing.T) {
|
|||
assert.Equal(t, "Carrots", a2.Arguments[0].Title, "Arg title is set to name")
|
||||
assert.Equal(t, "Waffle", a2.Arguments[0].Choices[0].Title, "Choice title is set to name")
|
||||
}
|
||||
|
||||
func TestSanitizeConfigInlineDashboardActions(t *testing.T) {
|
||||
c := DefaultConfig()
|
||||
|
||||
inline := &Action{
|
||||
Shell: "date",
|
||||
}
|
||||
|
||||
dashboardActionTitle := "Inline Dashboard Action"
|
||||
|
||||
c.Dashboards = []*DashboardComponent{
|
||||
{
|
||||
Title: "My Dashboard",
|
||||
Contents: []*DashboardComponent{
|
||||
{
|
||||
Title: dashboardActionTitle,
|
||||
InlineAction: inline,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.Sanitize()
|
||||
|
||||
// Inline action should have been appended to the global Actions slice.
|
||||
assert.GreaterOrEqual(t, len(c.Actions), 1, "At least one action should exist after sanitization")
|
||||
|
||||
// It should be discoverable by the dashboard component title when no explicit title was set.
|
||||
found := c.findAction(dashboardActionTitle)
|
||||
if assert.NotNil(t, found, "Inline dashboard action should be discoverable by title") {
|
||||
assert.Equal(t, dashboardActionTitle, found.Title, "Inline action title should default from dashboard component title")
|
||||
assert.Equal(t, 3, found.Timeout, "Inline action should have default timeout applied")
|
||||
assert.NotEmpty(t, found.Icon, "Inline action should have default icon applied")
|
||||
assert.NotEmpty(t, found.ID, "Inline action should have a generated ID")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func findEntityTitle(data any) string {
|
|||
keys[lookupKey] = k
|
||||
}
|
||||
|
||||
for _, key := range []string{"title", "name", "id"} {
|
||||
for _, key := range []string{"title", "name", "id", "hostname", "host", "label"} {
|
||||
if lookupKey, exists := keys[strings.ToLower(key)]; exists {
|
||||
if value, ok := mapData[lookupKey]; ok {
|
||||
if valueStr, ok := value.(string); ok {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func migrateLegacyEntityProperties(rawShellCommand string) string {
|
|||
log.WithFields(log.Fields{
|
||||
"old": entityName,
|
||||
"new": ".CurrentEntity",
|
||||
}).Warnf("Legacy entity variable name found, changing to CurrentEntity")
|
||||
}).Debugf("Legacy entity variable name found, changing to CurrentEntity")
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ func migrateLegacyEntityProperties(rawShellCommand string) string {
|
|||
log.WithFields(log.Fields{
|
||||
"old": argName,
|
||||
"new": ".CurrentEntity." + argName,
|
||||
}).Warnf("Legacy variable name found, changing to CurrentEntity")
|
||||
}).Debugf("Legacy variable name found, changing to CurrentEntity")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ func migrateLegacyArgumentNames(rawShellCommand string) string {
|
|||
log.WithFields(log.Fields{
|
||||
"old": argName,
|
||||
"new": ".Arguments." + argName,
|
||||
}).Warnf("Legacy variable name found, changing to Argument")
|
||||
}).Debugf("Legacy variable name found, changing to Argument")
|
||||
|
||||
rawShellCommand = strings.ReplaceAll(rawShellCommand, argName, ".Arguments."+argName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,15 @@ func findDashboardActionTitles(req *RebuildActionMapRequest) {
|
|||
//gocyclo:ignore
|
||||
func recurseDashboardForActionTitles(component *config.DashboardComponent, req *RebuildActionMapRequest) {
|
||||
for _, sub := range component.Contents {
|
||||
if sub.Type == "link" || sub.Type == "" {
|
||||
if sub.InlineAction != nil {
|
||||
title := sub.Title
|
||||
if title == "" {
|
||||
title = sub.InlineAction.Title
|
||||
}
|
||||
if title != "" {
|
||||
req.DashboardActionTitles = append(req.DashboardActionTitles, title)
|
||||
}
|
||||
} else if sub.Type == "link" || sub.Type == "" {
|
||||
req.DashboardActionTitles = append(req.DashboardActionTitles, sub.Title)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue