feat: more intelligent reconnect method (#1045)
This commit is contained in:
commit
6b3896b1c6
|
|
@ -21,4 +21,5 @@ webui
|
|||
webui.dev
|
||||
sessions.yaml
|
||||
docs/build/
|
||||
build/
|
||||
build/
|
||||
.cursor
|
||||
|
|
|
|||
|
|
@ -2,52 +2,175 @@ import { buttonResults } from '../resources/vue/stores/buttonResults.js'
|
|||
import { rateLimits } from '../resources/vue/stores/rateLimits.js'
|
||||
import { connectionState } from '../resources/vue/stores/connectionState.js'
|
||||
|
||||
const RECONNECT_DELAY_MS = 10000
|
||||
const RECONNECT_DELAYS_MS = [200, 1000, 2000, 4000, 8000, 16000, 32000]
|
||||
const BANNER_DELAY_MS = 2000
|
||||
|
||||
export function initWebsocket () {
|
||||
window.addEventListener('EventOutputChunk', onOutputChunk)
|
||||
window.addEventListener('EventExecutionStarted', onExecutionChanged)
|
||||
window.addEventListener('EventExecutionFinished', onExecutionChanged)
|
||||
let reconnectAttempt = 0
|
||||
let reconnectTimer = null
|
||||
let listenersInitialized = false
|
||||
let eventStreamGeneration = 0
|
||||
let eventStreamAbortController = null
|
||||
|
||||
function shouldConnectEventStream () {
|
||||
return window.initResponse && !window.initResponse.loginRequired
|
||||
}
|
||||
|
||||
export function stopEventStream () {
|
||||
eventStreamGeneration++
|
||||
if (eventStreamAbortController != null) {
|
||||
eventStreamAbortController.abort()
|
||||
eventStreamAbortController = null
|
||||
}
|
||||
|
||||
if (reconnectTimer != null) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
reconnectAttempt = 0
|
||||
connectionState.connected = false
|
||||
connectionState.reconnecting = false
|
||||
connectionState.scheduledReconnectDelayMs = 0
|
||||
connectionState.nextReconnectAt = null
|
||||
connectionState.showDisconnectedBanner = false
|
||||
window.websocketAvailable = false
|
||||
}
|
||||
|
||||
export function connectEventStreamIfNeeded () {
|
||||
if (!shouldConnectEventStream()) {
|
||||
stopEventStream()
|
||||
return
|
||||
}
|
||||
|
||||
if (connectionState.connected || reconnectTimer != null) {
|
||||
return
|
||||
}
|
||||
|
||||
reconnectWebsocket()
|
||||
}
|
||||
|
||||
export function initWebsocket () {
|
||||
if (!listenersInitialized) {
|
||||
window.addEventListener('EventOutputChunk', onOutputChunk)
|
||||
window.addEventListener('EventExecutionStarted', onExecutionChanged)
|
||||
window.addEventListener('EventExecutionFinished', onExecutionChanged)
|
||||
window.addEventListener('pagehide', stopEventStream)
|
||||
listenersInitialized = true
|
||||
}
|
||||
|
||||
connectEventStreamIfNeeded()
|
||||
}
|
||||
|
||||
window.websocketAvailable = false
|
||||
|
||||
async function reconnectWebsocket () {
|
||||
if (window.websocketAvailable) {
|
||||
export function requestReconnectNow () {
|
||||
if (!shouldConnectEventStream()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (connectionState.connected) {
|
||||
return
|
||||
}
|
||||
|
||||
if (reconnectTimer != null) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
reconnectAttempt = 0
|
||||
scheduleReconnect(RECONNECT_DELAYS_MS[0])
|
||||
}
|
||||
|
||||
function scheduleReconnect (delayMs) {
|
||||
if (reconnectTimer != null) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
connectionState.scheduledReconnectDelayMs = delayMs
|
||||
connectionState.nextReconnectAt = delayMs > 0 ? Date.now() + delayMs : null
|
||||
updateBannerVisibility()
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = null
|
||||
reconnectWebsocket()
|
||||
}, delayMs)
|
||||
}
|
||||
|
||||
function updateBannerVisibility () {
|
||||
if (connectionState.connected) {
|
||||
connectionState.showDisconnectedBanner = false
|
||||
return
|
||||
}
|
||||
|
||||
connectionState.showDisconnectedBanner = connectionState.scheduledReconnectDelayMs >= BANNER_DELAY_MS
|
||||
}
|
||||
|
||||
async function reconnectWebsocket () {
|
||||
if (!shouldConnectEventStream()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (connectionState.connected) {
|
||||
return
|
||||
}
|
||||
|
||||
const streamGeneration = ++eventStreamGeneration
|
||||
if (eventStreamAbortController != null) {
|
||||
eventStreamAbortController.abort()
|
||||
}
|
||||
eventStreamAbortController = new AbortController()
|
||||
|
||||
connectionState.reconnecting = true
|
||||
connectionState.connected = false
|
||||
if (connectionState.disconnectedAt == null) {
|
||||
connectionState.disconnectedAt = Date.now()
|
||||
}
|
||||
connectionState.nextReconnectAt = null
|
||||
connectionState.scheduledReconnectDelayMs = 0
|
||||
|
||||
try {
|
||||
window.websocketAvailable = true
|
||||
const stream = window.client.eventStream()
|
||||
const stream = window.client.eventStream({}, { signal: eventStreamAbortController.signal })
|
||||
connectionState.connected = true
|
||||
connectionState.reconnecting = false
|
||||
connectionState.disconnectedAt = null
|
||||
connectionState.nextReconnectAt = null
|
||||
connectionState.scheduledReconnectDelayMs = 0
|
||||
connectionState.showDisconnectedBanner = false
|
||||
for await (const e of stream) {
|
||||
connectionState.disconnectedAt = null
|
||||
if (streamGeneration !== eventStreamGeneration) {
|
||||
return
|
||||
}
|
||||
if (reconnectAttempt !== 0) {
|
||||
reconnectAttempt = 0
|
||||
}
|
||||
handleEvent(e)
|
||||
}
|
||||
} catch (err) {
|
||||
if (streamGeneration !== eventStreamGeneration) {
|
||||
return
|
||||
}
|
||||
console.error('Websocket connection failed: ', err)
|
||||
}
|
||||
|
||||
if (streamGeneration !== eventStreamGeneration) {
|
||||
return
|
||||
}
|
||||
|
||||
window.websocketAvailable = false
|
||||
connectionState.connected = false
|
||||
connectionState.reconnecting = false
|
||||
connectionState.disconnectedAt = connectionState.disconnectedAt ?? Date.now()
|
||||
connectionState.nextReconnectAt = Date.now() + RECONNECT_DELAY_MS
|
||||
console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...')
|
||||
setTimeout(() => {
|
||||
reconnectWebsocket()
|
||||
}, RECONNECT_DELAY_MS)
|
||||
|
||||
const delay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)]
|
||||
reconnectAttempt++
|
||||
console.log('Reconnecting websocket in ' + delay + 'ms...')
|
||||
|
||||
if (!shouldConnectEventStream()) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleReconnect(delay)
|
||||
}
|
||||
|
||||
async function refreshInitAfterConfigChange () {
|
||||
|
|
@ -83,6 +206,8 @@ function handleEvent (msg) {
|
|||
console.error('EventConfigChanged handler failed:', err)
|
||||
})
|
||||
break
|
||||
case 'EventHeartbeat':
|
||||
break
|
||||
case 'EventOutputChunk':
|
||||
case 'EventEntityChanged':
|
||||
window.dispatchEvent(j)
|
||||
|
|
@ -108,18 +233,21 @@ function onOutputChunk (evt) {
|
|||
}
|
||||
}
|
||||
|
||||
function onExecutionChanged (evt) {
|
||||
buttonResults[evt.payload.logEntry.executionTrackingId] = evt.payload.logEntry
|
||||
export function applyExecutionLogEntry (logEntry) {
|
||||
if (!logEntry?.executionTrackingId) {
|
||||
return
|
||||
}
|
||||
|
||||
const logEntry = evt.payload.logEntry
|
||||
buttonResults[logEntry.executionTrackingId] = logEntry
|
||||
|
||||
// Update rate limit store from logEntry if rate limit expiry datetime is provided
|
||||
if (logEntry && logEntry.datetimeRateLimitExpires && logEntry.bindingId) {
|
||||
// Parse datetime string "2006-01-02 15:04:05" and convert to Unix timestamp
|
||||
if (logEntry.datetimeRateLimitExpires && logEntry.bindingId) {
|
||||
const date = new Date(logEntry.datetimeRateLimitExpires.replace(' ', 'T') + 'Z')
|
||||
rateLimits[logEntry.bindingId] = date.getTime() / 1000
|
||||
} else if (logEntry && logEntry.bindingId) {
|
||||
// Clear rate limit if not set
|
||||
} else if (logEntry.bindingId) {
|
||||
rateLimits[logEntry.bindingId] = 0
|
||||
}
|
||||
}
|
||||
|
||||
function onExecutionChanged (evt) {
|
||||
applyExecutionLogEntry(evt.payload.logEntry)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1135,6 +1135,12 @@ export declare type EventStreamResponse = Message<"olivetin.api.v1.EventStreamRe
|
|||
*/
|
||||
value: EventOutputChunk;
|
||||
case: "outputChunk";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: olivetin.api.v1.EventHeartbeat heartbeat = 7;
|
||||
*/
|
||||
value: EventHeartbeat;
|
||||
case: "heartbeat";
|
||||
} | { case: undefined; value?: undefined };
|
||||
};
|
||||
|
||||
|
|
@ -1189,6 +1195,18 @@ export declare type EventConfigChanged = Message<"olivetin.api.v1.EventConfigCha
|
|||
*/
|
||||
export declare const EventConfigChangedSchema: GenMessage<EventConfigChanged>;
|
||||
|
||||
/**
|
||||
* @generated from message olivetin.api.v1.EventHeartbeat
|
||||
*/
|
||||
export declare type EventHeartbeat = Message<"olivetin.api.v1.EventHeartbeat"> & {
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message olivetin.api.v1.EventHeartbeat.
|
||||
* Use `create(EventHeartbeatSchema)` to create a new message.
|
||||
*/
|
||||
export declare const EventHeartbeatSchema: GenMessage<EventHeartbeat>;
|
||||
|
||||
/**
|
||||
* @generated from message olivetin.api.v1.EventExecutionFinished
|
||||
*/
|
||||
|
|
@ -1919,4 +1937,3 @@ export declare const OliveTinApiService: GenService<{
|
|||
output: typeof EntitySchema;
|
||||
},
|
||||
}>;
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,6 +29,8 @@
|
|||
<script setup>
|
||||
import { buttonResults } from './stores/buttonResults'
|
||||
import { rateLimits } from './stores/rateLimits'
|
||||
import { connectionState } from './stores/connectionState'
|
||||
import { requestReconnectNow, applyExecutionLogEntry } from '../../js/websocket.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { WorkoutRunIcon, TypeCursorIcon, ComputerTerminal01Icon, WorkHistoryIcon } from '@hugeicons/core-free-icons'
|
||||
|
|
@ -210,6 +212,28 @@ function getUniqueId() {
|
|||
}
|
||||
}
|
||||
|
||||
async function pollExecutionUntilDone (trackingId) {
|
||||
const pollIntervalMs = 500
|
||||
const pollTimeoutMs = 10 * 60 * 1000
|
||||
const deadline = Date.now() + pollTimeoutMs
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const result = await window.client.executionStatus({ executionTrackingId: trackingId })
|
||||
if (result.logEntry) {
|
||||
applyExecutionLogEntry(result.logEntry)
|
||||
if (result.logEntry.executionFinished) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to poll execution status:', err)
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
||||
}
|
||||
}
|
||||
|
||||
async function startAction(actionArgs) {
|
||||
buttonClasses.value = [] // Removes old animation classes
|
||||
|
||||
|
|
@ -234,8 +258,19 @@ async function startAction(actionArgs) {
|
|||
}
|
||||
)
|
||||
|
||||
requestReconnectNow()
|
||||
|
||||
try {
|
||||
await window.client.startAction(startActionArgs)
|
||||
const response = await window.client.startAction(startActionArgs)
|
||||
const trackingId = response.executionTrackingId || startActionArgs.uniqueTrackingId
|
||||
|
||||
if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
|
||||
router.push(`/logs/${trackingId}`)
|
||||
}
|
||||
|
||||
if (!connectionState.connected) {
|
||||
await pollExecutionUntilDone(trackingId)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to start action:', err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ import Sidebar from 'picocrank/vue/components/Sidebar.vue';
|
|||
import Navigation from 'picocrank/vue/components/Navigation.vue';
|
||||
import Header from 'picocrank/vue/components/Header.vue';
|
||||
import ConnectionBanner from './components/ConnectionBanner.vue';
|
||||
import { connectEventStreamIfNeeded } from '../../js/websocket.js';
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
||||
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
||||
|
|
@ -237,9 +238,12 @@ function updateHeaderFromInit() {
|
|||
applyTheme()
|
||||
|
||||
if (window.initResponse.loginRequired) {
|
||||
connectEventStreamIfNeeded()
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
connectEventStreamIfNeeded()
|
||||
}
|
||||
|
||||
function renderNavigation() {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@
|
|||
</div>
|
||||
<div class = "dashboard-row" v-for="component in dashboard.contents" :key="component.title">
|
||||
<h2 v-if = "dashboard.title != 'Default'">
|
||||
<router-link
|
||||
v-if="component.entityType && component.entityKey"
|
||||
:to="{
|
||||
name: 'EntityDetails',
|
||||
params: {
|
||||
<router-link
|
||||
v-if="component.entityType && component.entityKey"
|
||||
:to="{
|
||||
name: 'EntityDetails',
|
||||
params: {
|
||||
entityType: component.entityType,
|
||||
entityKey: component.entityKey
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
|
||||
<script setup>
|
||||
import DashboardComponent from './components/DashboardComponent.vue'
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Loading03Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
|
|
@ -118,31 +118,31 @@ async function getDashboard() {
|
|||
const request = {
|
||||
title: title,
|
||||
}
|
||||
|
||||
|
||||
if (props.entityType && props.entityKey) {
|
||||
request.entityType = props.entityType
|
||||
request.entityKey = props.entityKey
|
||||
}
|
||||
|
||||
|
||||
const ret = await window.client.getDashboard(request)
|
||||
|
||||
if (!ret || !ret.dashboard) {
|
||||
throw new Error('No dashboard found')
|
||||
}
|
||||
|
||||
dashboard.value = ret.dashboard
|
||||
dashboard.value = ret.dashboard
|
||||
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
|
||||
document.title = ret.dashboard.title + ' - ' + pageTitle
|
||||
|
||||
|
||||
// Clear any previous init error since we successfully loaded
|
||||
initError.value = null
|
||||
|
||||
|
||||
// Stop the loading timer once dashboard is loaded
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
|
||||
// Set attribute to indicate dashboard is loaded successfully
|
||||
document.body.setAttribute('loaded-dashboard', title || 'default')
|
||||
} catch (e) {
|
||||
|
|
@ -151,25 +151,35 @@ async function getDashboard() {
|
|||
dashboard.value = { title: title || 'Default', contents: [] }
|
||||
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
|
||||
document.title = 'Error - ' + pageTitle
|
||||
|
||||
|
||||
// Stop the loading timer on error
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
|
||||
|
||||
// Set attribute even on error so tests can proceed
|
||||
document.body.setAttribute('loaded-dashboard', title || 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function waitForInitAndLoadDashboard() {
|
||||
// Start the loading timer
|
||||
document.body.removeAttribute('loaded-dashboard')
|
||||
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
if (checkInitInterval) {
|
||||
clearInterval(checkInitInterval)
|
||||
checkInitInterval = null
|
||||
}
|
||||
|
||||
loadingTime.value = 0
|
||||
loadingTimer = setInterval(() => {
|
||||
loadingTime.value++
|
||||
}, 1000)
|
||||
|
||||
|
||||
// Check if init has completed successfully
|
||||
if (window.initResponse) {
|
||||
getDashboard()
|
||||
|
|
@ -206,7 +216,17 @@ onMounted(() => {
|
|||
waitForInitAndLoadDashboard()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.title, props.entityType, props.entityKey],
|
||||
() => {
|
||||
dashboard.value = null
|
||||
waitForInitAndLoadDashboard()
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.removeAttribute('loaded-dashboard')
|
||||
|
||||
// Clean up the timers when component is unmounted
|
||||
if (loadingTimer) {
|
||||
clearInterval(loadingTimer)
|
||||
|
|
@ -295,4 +315,4 @@ fieldset {
|
|||
background-color: var(--bg-hover, #222);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<span id="connection-banner" v-if="!connectionState.connected" class="inline-notification critical user-info-connection">
|
||||
<span id="connection-banner" v-if="connectionState.showDisconnectedBanner" class="inline-notification critical user-info-connection">
|
||||
<span class="connection-banner-sr-only" role="status">{{ staticAnnouncement }}</span>
|
||||
<span aria-hidden="true">
|
||||
<a :href="websocketDocsUrl" target="_blank" rel="noopener noreferrer" class="connection-banner-link">{{ linkText }}</a>{{ bannerSuffix }}
|
||||
|
|
@ -35,12 +35,12 @@ function formatShortTime(ts) {
|
|||
|
||||
const now = ref(Date.now())
|
||||
let ticker = null
|
||||
watch(() => connectionState.connected, (connected) => {
|
||||
watch(() => connectionState.showDisconnectedBanner, (showBanner) => {
|
||||
if (ticker) {
|
||||
clearInterval(ticker)
|
||||
ticker = null
|
||||
}
|
||||
if (!connected) {
|
||||
if (showBanner) {
|
||||
now.value = Date.now()
|
||||
ticker = setInterval(() => { now.value = Date.now() }, 1000)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import Dashboard from './Dashboard.vue'
|
||||
|
||||
import { Wrench01Icon } from '@hugeicons/core-free-icons'
|
||||
import { LeftToRightListDashIcon } from '@hugeicons/core-free-icons'
|
||||
import { CellsIcon } from '@hugeicons/core-free-icons'
|
||||
|
|
@ -9,13 +11,13 @@ const routes = [
|
|||
{
|
||||
path: '/',
|
||||
name: 'Actions',
|
||||
component: () => import('./Dashboard.vue'),
|
||||
component: Dashboard,
|
||||
meta: { title: 'Actions', icon: DashboardSquare01Icon }
|
||||
},
|
||||
{
|
||||
path: '/dashboards/:title/:entityType?/:entityKey?',
|
||||
name: 'Dashboard',
|
||||
component: () => import('./Dashboard.vue'),
|
||||
component: Dashboard,
|
||||
props: true,
|
||||
meta: { title: 'Dashboard' }
|
||||
},
|
||||
|
|
@ -30,7 +32,7 @@ const routes = [
|
|||
path: '/logs',
|
||||
name: 'Logs',
|
||||
component: () => import('./views/LogsListView.vue'),
|
||||
meta: {
|
||||
meta: {
|
||||
title: 'Logs',
|
||||
icon: LeftToRightListDashIcon
|
||||
}
|
||||
|
|
@ -39,7 +41,7 @@ const routes = [
|
|||
path: '/logs/calendar',
|
||||
name: 'LogsCalendar',
|
||||
component: () => import('./views/LogsCalendarView.vue'),
|
||||
meta: {
|
||||
meta: {
|
||||
title: 'Logs Calendar',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
|
|
@ -51,7 +53,7 @@ const routes = [
|
|||
path: '/entities',
|
||||
name: 'Entities',
|
||||
component: () => import('./views/EntitiesView.vue'),
|
||||
meta: {
|
||||
meta: {
|
||||
title: 'Entities',
|
||||
icon: CellsIcon
|
||||
}
|
||||
|
|
@ -61,8 +63,8 @@ const routes = [
|
|||
name: 'EntityDetails',
|
||||
component: () => import('./views/EntityDetailsView.vue'),
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'OliveTin - Entity Details',
|
||||
meta: {
|
||||
title: 'OliveTin - Entity Details',
|
||||
breadcrumb: [
|
||||
{ name: "Entities", href: "/entities" },
|
||||
{ name: "Entity Details" }
|
||||
|
|
@ -74,8 +76,8 @@ const routes = [
|
|||
name: 'Execution',
|
||||
component: () => import('./views/ExecutionView.vue'),
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'Execution',
|
||||
meta: {
|
||||
title: 'Execution',
|
||||
breadcrumb: [
|
||||
{ name: "Logs", href: "/logs" },
|
||||
{ name: "Execution" },
|
||||
|
|
@ -87,7 +89,7 @@ const routes = [
|
|||
name: 'ActionDetails',
|
||||
component: () => import('./views/ActionDetailsView.vue'),
|
||||
props: true,
|
||||
meta: {
|
||||
meta: {
|
||||
title: 'Action Details',
|
||||
breadcrumb: [
|
||||
{ name: "Actions", href: "/" },
|
||||
|
|
@ -112,7 +114,7 @@ const routes = [
|
|||
path: '/diagnostics',
|
||||
name: 'Diagnostics',
|
||||
component: () => import('./views/DiagnosticsView.vue'),
|
||||
meta: {
|
||||
meta: {
|
||||
title: 'Diagnostics',
|
||||
icon: Wrench01Icon
|
||||
}
|
||||
|
|
@ -163,7 +165,7 @@ router.beforeEach((to, from, next) => {
|
|||
router.beforeEach((to, from, next) => {
|
||||
// Check if user is authenticated for protected routes
|
||||
const isAuthenticated = window.isAuthenticated || true // Default to true for now
|
||||
|
||||
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
next('/login')
|
||||
} else {
|
||||
|
|
@ -171,4 +173,4 @@ router.beforeEach((to, from, next) => {
|
|||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
export default router
|
||||
|
|
|
|||
|
|
@ -4,5 +4,7 @@ export const connectionState = reactive({
|
|||
connected: false,
|
||||
reconnecting: false,
|
||||
disconnectedAt: null,
|
||||
nextReconnectAt: null
|
||||
nextReconnectAt: null,
|
||||
scheduledReconnectDelayMs: 0,
|
||||
showDisconnectedBanner: false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ import Pagination from 'picocrank/vue/components/Pagination.vue'
|
|||
import Section from 'picocrank/vue/components/Section.vue'
|
||||
import ActionIconGlyph from '../components/ActionIconGlyph.vue'
|
||||
import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
|
||||
import { requestReconnectNow } from '../../../js/websocket.js'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -290,6 +291,7 @@ async function startAction() {
|
|||
}
|
||||
|
||||
try {
|
||||
requestReconnectNow()
|
||||
const args = {
|
||||
"bindingId": action.value.bindingId,
|
||||
"arguments": []
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button name="start" type="submit" :disabled="hasConfirmation && !confirmationChecked">
|
||||
<button name="start" type="submit" :disabled="!formReady || (hasConfirmation && !confirmationChecked)">
|
||||
Start
|
||||
</button>
|
||||
<button name="cancel" type="button" @click="handleCancel">
|
||||
|
|
@ -58,8 +58,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { requestReconnectNow } from '../../../js/websocket.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ const hasConfirmation = ref(false)
|
|||
const formErrors = ref({})
|
||||
const actionArguments = ref([])
|
||||
const popupOnStart = ref('')
|
||||
const formReady = ref(false)
|
||||
|
||||
// Computed properties
|
||||
|
||||
|
|
@ -86,23 +88,27 @@ const props = defineProps({
|
|||
|
||||
// Methods
|
||||
async function setup() {
|
||||
const ret = await window.client.getActionBinding({
|
||||
bindingId: props.bindingId
|
||||
})
|
||||
formReady.value = false
|
||||
document.body.removeAttribute('loaded-argument-form')
|
||||
|
||||
const action = ret.action
|
||||
try {
|
||||
const ret = await window.client.getActionBinding({
|
||||
bindingId: props.bindingId
|
||||
})
|
||||
|
||||
title.value = action.title
|
||||
icon.value = action.icon
|
||||
popupOnStart.value = action.popupOnStart || ''
|
||||
actionArguments.value = action.arguments || []
|
||||
argValues.value = {}
|
||||
formErrors.value = {}
|
||||
confirmationChecked.value = false
|
||||
hasConfirmation.value = false
|
||||
const action = ret.action
|
||||
|
||||
// Initialize values from query params or defaults
|
||||
actionArguments.value.forEach(arg => {
|
||||
title.value = action.title
|
||||
icon.value = action.icon
|
||||
popupOnStart.value = action.popupOnStart || ''
|
||||
actionArguments.value = action.arguments || []
|
||||
argValues.value = {}
|
||||
formErrors.value = {}
|
||||
confirmationChecked.value = false
|
||||
hasConfirmation.value = false
|
||||
|
||||
// Initialize values from query params or defaults
|
||||
actionArguments.value.forEach(arg => {
|
||||
if (arg.type === 'confirmation') {
|
||||
hasConfirmation.value = true
|
||||
const paramValue = getQueryParamValue(arg.name)
|
||||
|
|
@ -129,14 +135,20 @@ async function setup() {
|
|||
argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Run initial validation on all fields after DOM is updated
|
||||
await nextTick()
|
||||
for (const arg of actionArguments.value) {
|
||||
if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation' && arg.type !== 'checkbox') {
|
||||
await validateArgument(arg, argValues.value[arg.name] || '')
|
||||
// Run initial validation on all fields after DOM is updated
|
||||
await nextTick()
|
||||
for (const arg of actionArguments.value) {
|
||||
if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation' && arg.type !== 'checkbox') {
|
||||
await validateArgument(arg, argValues.value[arg.name] || '')
|
||||
}
|
||||
}
|
||||
|
||||
formReady.value = true
|
||||
document.body.setAttribute('loaded-argument-form', props.bindingId)
|
||||
} catch (err) {
|
||||
console.error('Failed to load argument form:', err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +392,7 @@ async function startAction(actionArgs) {
|
|||
}
|
||||
|
||||
try {
|
||||
requestReconnectNow()
|
||||
const response = await window.client.startAction(startActionArgs)
|
||||
console.log('Action started successfully with tracking ID:', response.executionTrackingId)
|
||||
return response
|
||||
|
|
@ -392,6 +405,10 @@ async function startAction(actionArgs) {
|
|||
async function handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!formReady.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (popupOnStart.value === 'history') {
|
||||
router.push(`/action/${props.bindingId}`)
|
||||
return
|
||||
|
|
@ -468,6 +485,10 @@ defineExpose({
|
|||
onMounted(() => {
|
||||
setup()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.removeAttribute('loaded-argument-form')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ import { HugeiconsIcon } from '@hugeicons/vue'
|
|||
import { WorkoutRunIcon, Cancel02Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { buttonResults } from '../stores/buttonResults'
|
||||
import { requestReconnectNow } from '../../../js/websocket.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
|
@ -177,6 +178,7 @@ async function rerunAction() {
|
|||
}
|
||||
|
||||
try {
|
||||
requestReconnectNow()
|
||||
const startActionArgs = {
|
||||
"bindingId": bindingId,
|
||||
"arguments": []
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import fs from 'fs'
|
|||
import { expect } from 'chai'
|
||||
import { Condition } from 'selenium-webdriver'
|
||||
|
||||
export const DEFAULT_UI_WAIT_MS = 3000
|
||||
|
||||
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.
|
||||
|
|
@ -10,11 +12,11 @@ export async function getActionButtons () {
|
|||
}
|
||||
|
||||
export async function getExecutionDialogOutput() {
|
||||
await webdriver.wait(new Condition('Dialog with long int is visible', async () => {
|
||||
await webdriver.wait(new Condition('Dialog with long int is visible', async () => {
|
||||
const dialog = await webdriver.findElement({ id: 'execution-results-popup' })
|
||||
return await dialog.isDisplayed()
|
||||
}));
|
||||
|
||||
|
||||
const ret = await webdriver.executeScript('return window.logEntries.get(window.executionDialog.executionTrackingId).output')
|
||||
|
||||
return ret
|
||||
|
|
@ -46,20 +48,59 @@ export function takeScreenshot (webdriver, title) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function getRootAndWait() {
|
||||
await webdriver.get(runner.baseUrl())
|
||||
await webdriver.wait(new Condition('wait for loaded-dashboard', async function() {
|
||||
export async function waitForDashboardLoaded(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
||||
await webdriver.wait(new Condition('wait for loaded-dashboard', async function () {
|
||||
const body = await webdriver.findElement(By.tagName('body'))
|
||||
const attr = await body.getAttribute('loaded-dashboard')
|
||||
|
||||
console.log('loaded-dashboard: ', attr)
|
||||
|
||||
if (attr) {
|
||||
return true
|
||||
} else {
|
||||
return attr != null && attr !== ''
|
||||
}), timeoutMs)
|
||||
}
|
||||
|
||||
export async function waitForLogsPage(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
||||
await webdriver.wait(new Condition('wait for logs page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/') && !url.endsWith('/logs')
|
||||
}), timeoutMs)
|
||||
}
|
||||
|
||||
export async function waitForArgumentFormPage(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
||||
await webdriver.wait(new Condition('wait for argument form page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
||||
}), timeoutMs)
|
||||
}
|
||||
|
||||
export async function waitForArgumentFormReady(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
||||
await webdriver.wait(new Condition('wait for argument form ready', async () => {
|
||||
const body = await webdriver.findElement(By.tagName('body'))
|
||||
const attr = await body.getAttribute('loaded-argument-form')
|
||||
return attr != null && attr !== ''
|
||||
}), timeoutMs)
|
||||
}
|
||||
|
||||
export async function waitForExecutionComplete(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
||||
await webdriver.wait(new Condition('wait for execution status', async () => {
|
||||
const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
|
||||
return statusElements.length > 0
|
||||
}), timeoutMs)
|
||||
|
||||
await webdriver.wait(new Condition('wait for execution to finish', async () => {
|
||||
try {
|
||||
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
|
||||
const statusText = await statusElement.getText()
|
||||
return !statusText.includes('Still running')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}))
|
||||
}), timeoutMs)
|
||||
}
|
||||
|
||||
export async function getRootAndWait() {
|
||||
await webdriver.get(runner.baseUrl())
|
||||
await waitForDashboardLoaded()
|
||||
}
|
||||
|
||||
export async function closeSidebar() {
|
||||
|
|
@ -82,7 +123,7 @@ export async function closeSidebar() {
|
|||
console.log('Sidebar closed, left is: *' + left, left === neededLeft ? ' (as expected)' : '')
|
||||
return left === neededLeft
|
||||
}
|
||||
}), 10000); // Wait up to 10 seconds for the sidebar to close
|
||||
}), DEFAULT_UI_WAIT_MS)
|
||||
}
|
||||
|
||||
export async function openSidebar() {
|
||||
|
|
@ -103,7 +144,7 @@ export async function openSidebar() {
|
|||
console.log('Sidebar opened, left is: ', left)
|
||||
return true
|
||||
}
|
||||
}));
|
||||
}), DEFAULT_UI_WAIT_MS)
|
||||
}
|
||||
|
||||
export async function getNavigationLinks() {
|
||||
|
|
@ -123,7 +164,7 @@ export async function requireExecutionDialogStatus (webdriver, expected) {
|
|||
console.log('Waiting for domStatus text to be: ', expected, ', it is currently: ', actual)
|
||||
return false
|
||||
}
|
||||
}))
|
||||
}), DEFAULT_UI_WAIT_MS)
|
||||
}
|
||||
|
||||
export async function findExecutionDialog (webdriver) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ class OliveTinTestRunner {
|
|||
|
||||
class OliveTinTestRunnerStartLocalProcess extends OliveTinTestRunner {
|
||||
async start (cfg) {
|
||||
if (this.ot != null && this.ot.exitCode == null) {
|
||||
await this.stop()
|
||||
}
|
||||
|
||||
let stdout = ""
|
||||
let stderr = ""
|
||||
|
||||
|
|
@ -94,13 +98,23 @@ class OliveTinTestRunnerStartLocalProcess extends OliveTinTestRunner {
|
|||
}
|
||||
|
||||
async stop () {
|
||||
if ((await this.ot.exitCode) != null) {
|
||||
console.log(" OliveTin local process tried stop(), but it already exited with code", this.ot.exitCode)
|
||||
} else {
|
||||
await this.ot.kill()
|
||||
console.log(" OliveTin local process killed")
|
||||
if (this.ot == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.ot.exitCode != null) {
|
||||
console.log(' OliveTin local process tried stop(), but it already exited with code', this.ot.exitCode)
|
||||
} else {
|
||||
const closed = new Promise((resolve) => {
|
||||
this.ot.once('close', resolve)
|
||||
})
|
||||
this.ot.kill('SIGTERM')
|
||||
await closed
|
||||
console.log(' OliveTin local process killed')
|
||||
}
|
||||
|
||||
this.ot = null
|
||||
|
||||
if (process.env.CI === 'true') {
|
||||
// GitHub runners seem to need a bit more time to clean up
|
||||
await new Promise((res) => setTimeout(res, 3000))
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ import { describe, it, before, after } from 'mocha'
|
|||
import { expect } from 'chai'
|
||||
import { By, Condition } from 'selenium-webdriver'
|
||||
import {
|
||||
DEFAULT_UI_WAIT_MS,
|
||||
getRootAndWait,
|
||||
getActionButton,
|
||||
takeScreenshotOnFailure,
|
||||
getTerminalBuffer,
|
||||
waitForArgumentFormPage,
|
||||
waitForLogsPage,
|
||||
waitForExecutionComplete,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
async function openCheckboxArgumentForm() {
|
||||
|
|
@ -13,13 +17,7 @@ async function openCheckboxArgumentForm() {
|
|||
const btn = await getActionButton(webdriver, 'Test checkbox argument')
|
||||
await btn.click()
|
||||
|
||||
await webdriver.wait(
|
||||
new Condition('wait for argument form page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
||||
}),
|
||||
5000
|
||||
)
|
||||
await waitForArgumentFormPage()
|
||||
}
|
||||
|
||||
async function getCheckboxInput() {
|
||||
|
|
@ -31,42 +29,6 @@ async function submitCheckboxForm() {
|
|||
await submitButton.click()
|
||||
}
|
||||
|
||||
async function waitForLogsPage() {
|
||||
await webdriver.wait(
|
||||
new Condition('wait for logs page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/') && !url.endsWith('/logs')
|
||||
}),
|
||||
5000
|
||||
)
|
||||
}
|
||||
|
||||
async function waitForExecutionComplete() {
|
||||
await webdriver.wait(
|
||||
new Condition('wait for execution status', async () => {
|
||||
const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
|
||||
return statusElements.length > 0
|
||||
}),
|
||||
5000
|
||||
)
|
||||
|
||||
await webdriver.wait(
|
||||
new Condition('wait for execution to finish', async () => {
|
||||
try {
|
||||
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
|
||||
const statusText = await statusElement.getText()
|
||||
return !statusText.includes('Executing')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}),
|
||||
5000
|
||||
)
|
||||
|
||||
// Small delay to allow terminal to write output
|
||||
await webdriver.sleep(500)
|
||||
}
|
||||
|
||||
async function waitForTerminalOutput(expectedValue) {
|
||||
await webdriver.wait(
|
||||
new Condition(`wait for checkbox value ${expectedValue} in output`, async () => {
|
||||
|
|
@ -77,18 +39,18 @@ async function waitForTerminalOutput(expectedValue) {
|
|||
if (!terminalReady) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
const output = await getTerminalBuffer()
|
||||
if (!output) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return output.trim().includes(`Checkbox value: ${expectedValue}`)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}),
|
||||
5000
|
||||
DEFAULT_UI_WAIT_MS
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +80,6 @@ describe('config: checkbox', function () {
|
|||
})
|
||||
|
||||
it('Checkbox argument submits 0 by default when unchecked', async function () {
|
||||
this.timeout(15000)
|
||||
await openCheckboxArgumentForm()
|
||||
|
||||
const checkboxInput = await getCheckboxInput()
|
||||
|
|
@ -131,7 +92,6 @@ describe('config: checkbox', function () {
|
|||
})
|
||||
|
||||
it('Checkbox argument can be toggled and submitted', async function () {
|
||||
this.timeout(15000)
|
||||
await openCheckboxArgumentForm()
|
||||
|
||||
const checkboxInput = await getCheckboxInput()
|
||||
|
|
@ -146,5 +106,3 @@ describe('config: checkbox', function () {
|
|||
await waitForTerminalOutput('1')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { describe, it, before, after } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { By, Condition } from 'selenium-webdriver'
|
||||
import { By } from 'selenium-webdriver'
|
||||
import {
|
||||
getRootAndWait,
|
||||
getActionButton,
|
||||
takeScreenshotOnFailure,
|
||||
waitForArgumentFormPage,
|
||||
waitForLogsPage,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: datetime', function () {
|
||||
|
|
@ -27,14 +29,7 @@ describe('config: datetime', function () {
|
|||
|
||||
await btn.click()
|
||||
|
||||
// Wait for navigation to argument form page
|
||||
await webdriver.wait(
|
||||
new Condition('wait for argument form page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
||||
}),
|
||||
8000
|
||||
)
|
||||
await waitForArgumentFormPage()
|
||||
|
||||
// Find the datetime input field
|
||||
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
||||
|
|
@ -59,14 +54,7 @@ describe('config: datetime', function () {
|
|||
|
||||
await btn.click()
|
||||
|
||||
// Wait for navigation to argument form page
|
||||
await webdriver.wait(
|
||||
new Condition('wait for argument form page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
||||
}),
|
||||
8000
|
||||
)
|
||||
await waitForArgumentFormPage()
|
||||
|
||||
// Find the datetime input field
|
||||
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
||||
|
|
@ -74,7 +62,7 @@ describe('config: datetime', function () {
|
|||
// Set a datetime value (format: YYYY-MM-DDTHH:mm)
|
||||
// datetime-local returns values without seconds, backend will add :00
|
||||
const testDateTime = '2023-12-25T15:30'
|
||||
|
||||
|
||||
// Use JavaScript to set the value directly (more reliable for datetime-local inputs)
|
||||
await webdriver.executeScript(
|
||||
'arguments[0].value = arguments[1]',
|
||||
|
|
@ -101,18 +89,10 @@ describe('config: datetime', function () {
|
|||
)
|
||||
await submitButton.click()
|
||||
|
||||
// Wait for navigation to logs page
|
||||
await webdriver.wait(
|
||||
new Condition('wait for logs page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/')
|
||||
}),
|
||||
8000
|
||||
)
|
||||
await waitForLogsPage()
|
||||
|
||||
// Verify we're on the logs page (action was executed)
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
expect(url).to.include('/logs/')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ describe('config: enabledExpression', function () {
|
|||
}
|
||||
// Accept either decoded or encoded version (component should decode, but handle both)
|
||||
return attr === 'LightDashboard'
|
||||
}), 10000)
|
||||
}), 3000)
|
||||
|
||||
// Verify we got the correct dashboard (prefer decoded, but accept encoded)
|
||||
const body = await webdriver.findElement(By.tagName('body'))
|
||||
|
|
@ -60,7 +60,7 @@ describe('config: enabledExpression', function () {
|
|||
// Debug: Check what's on the page
|
||||
const dashboardRows = await webdriver.findElements(By.css('.dashboard-row'))
|
||||
console.log(`Found ${dashboardRows.length} dashboard rows`)
|
||||
|
||||
|
||||
for (let i = 0; i < dashboardRows.length; i++) {
|
||||
const row = dashboardRows[i]
|
||||
const h2Elements = await row.findElements(By.css('h2'))
|
||||
|
|
@ -82,25 +82,25 @@ describe('config: enabledExpression', function () {
|
|||
// Bedroom Light (powered_on: true) - Turn Off should be enabled, Turn On disabled
|
||||
let turnOnButton = null
|
||||
let turnOffButton = null
|
||||
|
||||
|
||||
for (const row of dashboardRows) {
|
||||
// Get the fieldset in this row
|
||||
const fieldsets = await row.findElements(By.css('fieldset'))
|
||||
if (fieldsets.length === 0) continue
|
||||
|
||||
|
||||
const buttons = await fieldsets[0].findElements(By.css('.action-button button'))
|
||||
|
||||
|
||||
// Check each button to identify which entity this row represents
|
||||
for (const btn of buttons) {
|
||||
const title = await btn.getAttribute('title')
|
||||
const disabled = await btn.getAttribute('disabled')
|
||||
const isEnabled = disabled === null
|
||||
|
||||
|
||||
if (title === 'Turn On Light' && isEnabled) {
|
||||
// This is the Living Room Light row (Turn On is enabled because powered_on: false)
|
||||
turnOnButton = btn
|
||||
}
|
||||
|
||||
|
||||
if (title === 'Turn Off Light' && isEnabled) {
|
||||
// This is the Bedroom Light row (Turn Off is enabled because powered_on: true)
|
||||
turnOffButton = btn
|
||||
|
|
@ -127,7 +127,7 @@ describe('config: enabledExpression', function () {
|
|||
await webdriver.get(runner.baseUrl())
|
||||
|
||||
// Wait for action buttons
|
||||
await webdriver.wait(until.elementLocated(By.css('.action-button')), 10000)
|
||||
await webdriver.wait(until.elementLocated(By.css('.action-button')), 3000)
|
||||
|
||||
// Find "Always Enabled Action" button
|
||||
const actionButtons = await webdriver.findElements(By.css('.action-button button'))
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { describe, it, before, after } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { By, until } from 'selenium-webdriver'
|
||||
import {
|
||||
getRootAndWait,
|
||||
takeScreenshot,
|
||||
import { By } from 'selenium-webdriver'
|
||||
import {
|
||||
getRootAndWait,
|
||||
takeScreenshotOnFailure,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: entities', function () {
|
||||
before(async function () {
|
||||
await runner.start('entities')
|
||||
await getRootAndWait()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
// Issue: https://github.com/OliveTin/OliveTin/issues/616
|
||||
import { describe, it, before, after } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { By, until, Condition } from 'selenium-webdriver'
|
||||
import { By } from 'selenium-webdriver'
|
||||
import {
|
||||
getRootAndWait,
|
||||
getActionButtons,
|
||||
takeScreenshotOnFailure,
|
||||
waitForLogsPage,
|
||||
waitForExecutionComplete,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: entities', function () {
|
||||
describe('config: entityFilesWithLongIntsUseStandardForm', function () {
|
||||
before(async function () {
|
||||
await runner.start('entityFilesWithLongIntsUseStandardForm')
|
||||
await getRootAndWait()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
|
|
@ -34,19 +37,9 @@ describe('config: entities', function () {
|
|||
expect(await buttonInt10.getAttribute('title')).to.be.equal('Test me INT with 10 numbers')
|
||||
await buttonInt10.click()
|
||||
|
||||
// Wait for navigation to execution view
|
||||
await webdriver.wait(new Condition('wait for execution view', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/') && !url.endsWith('/logs')
|
||||
}), 10000)
|
||||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Wait for execution to complete - look for the execution status
|
||||
await webdriver.wait(new Condition('wait for execution status', async () => {
|
||||
const statusElement = await webdriver.findElements(By.id('execution-dialog-status'))
|
||||
return statusElement.length > 0
|
||||
}), 15000)
|
||||
|
||||
// Check that the execution completed successfully by looking at the status
|
||||
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
|
||||
const statusText = await statusElement.getText()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,23 +2,54 @@ import { describe, it, before, after } from 'mocha'
|
|||
import { expect } from 'chai'
|
||||
import { By, Condition } from 'selenium-webdriver'
|
||||
import {
|
||||
DEFAULT_UI_WAIT_MS,
|
||||
getRootAndWait,
|
||||
getActionButton,
|
||||
takeScreenshotOnFailure,
|
||||
waitForDashboardLoaded,
|
||||
waitForLogsPage,
|
||||
waitForArgumentFormPage,
|
||||
waitForArgumentFormReady,
|
||||
waitForExecutionComplete,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
async function openArgumentForm() {
|
||||
async function ensureOnDashboard() {
|
||||
let url = await webdriver.getCurrentUrl()
|
||||
|
||||
if (url.includes('/logs/')) {
|
||||
const backButton = await webdriver.findElement(By.css('button[title="Go back"]'))
|
||||
await backButton.click()
|
||||
await webdriver.wait(
|
||||
new Condition('wait for argument form after logs back', async () => {
|
||||
const currentUrl = await webdriver.getCurrentUrl()
|
||||
return currentUrl.includes('/argumentForm')
|
||||
}),
|
||||
DEFAULT_UI_WAIT_MS
|
||||
)
|
||||
url = await webdriver.getCurrentUrl()
|
||||
}
|
||||
|
||||
if (url.includes('/argumentForm')) {
|
||||
const cancelButton = await webdriver.findElement(By.css('button[name="cancel"]'))
|
||||
await cancelButton.click()
|
||||
await waitForDashboardLoaded()
|
||||
}
|
||||
|
||||
const actionButtons = await webdriver.findElements(By.css('[title="Test suggestionsBrowserKey"]'))
|
||||
if (actionButtons.length === 1) {
|
||||
return
|
||||
}
|
||||
|
||||
await getRootAndWait()
|
||||
}
|
||||
|
||||
async function openArgumentForm() {
|
||||
await ensureOnDashboard()
|
||||
const btn = await getActionButton(webdriver, 'Test suggestionsBrowserKey')
|
||||
await btn.click()
|
||||
|
||||
await webdriver.wait(
|
||||
new Condition('wait for argument form page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
||||
}),
|
||||
5000
|
||||
)
|
||||
await waitForArgumentFormPage()
|
||||
await waitForArgumentFormReady()
|
||||
}
|
||||
|
||||
async function getTestInput() {
|
||||
|
|
@ -38,41 +69,6 @@ async function submitForm() {
|
|||
await submitButton.click()
|
||||
}
|
||||
|
||||
async function waitForLogsPage() {
|
||||
await webdriver.wait(
|
||||
new Condition('wait for logs page', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/') && !url.endsWith('/logs')
|
||||
}),
|
||||
15000
|
||||
)
|
||||
}
|
||||
|
||||
async function waitForExecutionComplete() {
|
||||
await webdriver.wait(
|
||||
new Condition('wait for execution status', async () => {
|
||||
const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
|
||||
return statusElements.length > 0
|
||||
}),
|
||||
5000
|
||||
)
|
||||
|
||||
await webdriver.wait(
|
||||
new Condition('wait for execution to finish', async () => {
|
||||
try {
|
||||
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
|
||||
const statusText = await statusElement.getText()
|
||||
return !statusText.includes('Executing')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}),
|
||||
5000
|
||||
)
|
||||
|
||||
await webdriver.sleep(500)
|
||||
}
|
||||
|
||||
async function getLocalStorageItem(key) {
|
||||
return await webdriver.executeScript(`return localStorage.getItem('${key}')`)
|
||||
}
|
||||
|
|
@ -84,6 +80,7 @@ async function clearLocalStorage() {
|
|||
describe('config: suggestionsBrowserKey', function () {
|
||||
before(async function () {
|
||||
await runner.start('suggestionsBrowserKey')
|
||||
await getRootAndWait()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
|
|
@ -113,11 +110,7 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
})
|
||||
|
||||
it('Submitting form saves value to localStorage', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
// Clear localStorage first
|
||||
await clearLocalStorage()
|
||||
|
||||
await openArgumentForm()
|
||||
|
||||
const input = await getTestInput()
|
||||
|
|
@ -131,7 +124,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Verify value was saved to localStorage
|
||||
const stored = await getLocalStorageItem('olivetin-suggestions-test-suggestions-key')
|
||||
expect(stored).to.not.be.null
|
||||
|
||||
|
|
@ -141,26 +133,20 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
})
|
||||
|
||||
it('Previously saved values appear in datalist', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
// First, save a value to localStorage
|
||||
const testValue = 'savedsuggestion456'
|
||||
await webdriver.executeScript(`
|
||||
const key = 'olivetin-suggestions-test-suggestions-key';
|
||||
localStorage.setItem(key, JSON.stringify(['${testValue}']));
|
||||
`)
|
||||
|
||||
// Open the form
|
||||
await openArgumentForm()
|
||||
|
||||
// Check that datalist exists and contains the saved value
|
||||
const datalist = await webdriver.findElement(By.id('testInput-choices'))
|
||||
expect(datalist).to.not.be.null
|
||||
|
||||
const options = await getDatalistOptions()
|
||||
expect(options.length).to.be.greaterThan(0)
|
||||
|
||||
// Check if the saved value appears in the datalist
|
||||
let foundValue = false
|
||||
for (const option of options) {
|
||||
const value = await option.getAttribute('value')
|
||||
|
|
@ -173,12 +159,8 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
})
|
||||
|
||||
it('Multiple submissions accumulate suggestions', async function () {
|
||||
this.timeout(20000)
|
||||
|
||||
// Clear localStorage first
|
||||
await clearLocalStorage()
|
||||
|
||||
// Submit first value
|
||||
await openArgumentForm()
|
||||
const input1 = await getTestInput()
|
||||
await input1.clear()
|
||||
|
|
@ -187,7 +169,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Submit second value
|
||||
await openArgumentForm()
|
||||
const input2 = await getTestInput()
|
||||
await input2.clear()
|
||||
|
|
@ -196,7 +177,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Verify both values are in localStorage
|
||||
const stored = await getLocalStorageItem('olivetin-suggestions-test-suggestions-key')
|
||||
expect(stored).to.not.be.null
|
||||
|
||||
|
|
@ -204,43 +184,33 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
expect(suggestions).to.be.an('array')
|
||||
expect(suggestions).to.include('firstvalue')
|
||||
expect(suggestions).to.include('secondvalue')
|
||||
expect(suggestions[0]).to.equal('secondvalue') // Most recent should be first
|
||||
expect(suggestions[0]).to.equal('secondvalue')
|
||||
})
|
||||
|
||||
it('Empty values are not saved to localStorage', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
// Clear localStorage first
|
||||
await clearLocalStorage()
|
||||
|
||||
await openArgumentForm()
|
||||
|
||||
const input = await getTestInput()
|
||||
// Leave input empty (or clear it if it has a default)
|
||||
await input.clear()
|
||||
|
||||
await submitForm()
|
||||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Verify empty value was not saved - localStorage should be null or empty-equivalent
|
||||
const stored = await getLocalStorageItem('olivetin-suggestions-test-suggestions-key')
|
||||
// Should be null OR empty JSON array string ("[]") OR parse to empty array
|
||||
if (stored !== null) {
|
||||
const suggestions = JSON.parse(stored)
|
||||
expect(suggestions).to.be.an('array')
|
||||
expect(suggestions).to.have.length(0)
|
||||
}
|
||||
// If stored is null, that's also acceptable - no assertion needed
|
||||
})
|
||||
|
||||
it('Suggestions are shared across inputs with the same suggestionsBrowserKey', async function () {
|
||||
this.timeout(20000)
|
||||
this.timeout(12000)
|
||||
|
||||
// Clear localStorage first
|
||||
await clearLocalStorage()
|
||||
|
||||
// Submit a value using the first input
|
||||
await openArgumentForm()
|
||||
const input1 = await getTestInput()
|
||||
await input1.clear()
|
||||
|
|
@ -249,10 +219,8 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Open the form again and verify the value appears in both datalists
|
||||
await openArgumentForm()
|
||||
|
||||
// Check first input's datalist
|
||||
const datalist1 = await webdriver.findElement(By.id('testInput-choices'))
|
||||
expect(datalist1).to.not.be.null
|
||||
const options1 = await getDatalistOptions('testInput')
|
||||
|
|
@ -266,7 +234,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
}
|
||||
expect(foundInInput1).to.be.true
|
||||
|
||||
// Check second input's datalist
|
||||
const datalist2 = await webdriver.findElement(By.id('testInput2-choices'))
|
||||
expect(datalist2).to.not.be.null
|
||||
const options2 = await getDatalistOptions('testInput2')
|
||||
|
|
@ -280,7 +247,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
}
|
||||
expect(foundInInput2).to.be.true
|
||||
|
||||
// Now submit a value using the second input
|
||||
const input2 = await getTestInput2()
|
||||
await input2.clear()
|
||||
await input2.sendKeys('sharedfrominput2')
|
||||
|
|
@ -288,10 +254,8 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
// Verify both values appear in both datalists
|
||||
await openArgumentForm()
|
||||
|
||||
// Check that both values are in the first input's datalist
|
||||
const options1After = await getDatalistOptions('testInput')
|
||||
let foundValue1 = false
|
||||
let foundValue2 = false
|
||||
|
|
@ -307,7 +271,6 @@ describe('config: suggestionsBrowserKey', function () {
|
|||
expect(foundValue1).to.be.true
|
||||
expect(foundValue2).to.be.true
|
||||
|
||||
// Check that both values are in the second input's datalist
|
||||
const options2After = await getDatalistOptions('testInput2')
|
||||
foundValue1 = false
|
||||
foundValue2 = false
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ import { describe, it, before, after } from 'mocha'
|
|||
import { expect } from 'chai'
|
||||
import { By, Condition } from 'selenium-webdriver'
|
||||
import {
|
||||
DEFAULT_UI_WAIT_MS,
|
||||
getRootAndWait,
|
||||
takeScreenshotOnFailure,
|
||||
getTerminalBuffer,
|
||||
waitForLogsPage,
|
||||
waitForExecutionComplete,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('config: xtermLinkHandling', function () {
|
||||
|
|
@ -26,32 +29,13 @@ describe('config: xtermLinkHandling', function () {
|
|||
await webdriver.wait(new Condition('wait for Echo URL button', async () => {
|
||||
const btns = await webdriver.findElements(By.css('[title="Echo URL"]'))
|
||||
return btns.length === 1
|
||||
}), 10000)
|
||||
}), DEFAULT_UI_WAIT_MS)
|
||||
|
||||
const echoUrlButton = await webdriver.findElement(By.css('[title="Echo URL"]'))
|
||||
await echoUrlButton.click()
|
||||
|
||||
await webdriver.wait(new Condition('wait for execution view', async () => {
|
||||
const url = await webdriver.getCurrentUrl()
|
||||
return url.includes('/logs/') && !url.endsWith('/logs')
|
||||
}), 10000)
|
||||
|
||||
await webdriver.wait(new Condition('wait for execution status', async () => {
|
||||
const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
|
||||
return statusElements.length > 0
|
||||
}), 5000)
|
||||
|
||||
await webdriver.wait(new Condition('wait for execution to finish', async () => {
|
||||
try {
|
||||
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
|
||||
const statusText = await statusElement.getText()
|
||||
return !statusText.includes('Executing')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}), 5000)
|
||||
|
||||
await webdriver.sleep(500)
|
||||
await waitForLogsPage()
|
||||
await waitForExecutionComplete()
|
||||
|
||||
const bufferText = await getTerminalBuffer()
|
||||
expect(bufferText).to.not.be.null
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ message EventStreamResponse {
|
|||
EventExecutionFinished execution_finished = 4;
|
||||
EventExecutionStarted execution_started = 5;
|
||||
EventOutputChunk output_chunk = 6;
|
||||
EventHeartbeat heartbeat = 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +273,7 @@ message EventOutputChunk {
|
|||
|
||||
message EventEntityChanged {}
|
||||
message EventConfigChanged {}
|
||||
message EventHeartbeat {}
|
||||
message EventExecutionFinished {
|
||||
LogEntry log_entry = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2518,6 +2518,7 @@ type EventStreamResponse struct {
|
|||
// *EventStreamResponse_ExecutionFinished
|
||||
// *EventStreamResponse_ExecutionStarted
|
||||
// *EventStreamResponse_OutputChunk
|
||||
// *EventStreamResponse_Heartbeat
|
||||
Event isEventStreamResponse_Event `protobuf_oneof:"event"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
|
@ -2605,6 +2606,15 @@ func (x *EventStreamResponse) GetOutputChunk() *EventOutputChunk {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *EventStreamResponse) GetHeartbeat() *EventHeartbeat {
|
||||
if x != nil {
|
||||
if x, ok := x.Event.(*EventStreamResponse_Heartbeat); ok {
|
||||
return x.Heartbeat
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isEventStreamResponse_Event interface {
|
||||
isEventStreamResponse_Event()
|
||||
}
|
||||
|
|
@ -2629,6 +2639,10 @@ type EventStreamResponse_OutputChunk struct {
|
|||
OutputChunk *EventOutputChunk `protobuf:"bytes,6,opt,name=output_chunk,json=outputChunk,proto3,oneof"`
|
||||
}
|
||||
|
||||
type EventStreamResponse_Heartbeat struct {
|
||||
Heartbeat *EventHeartbeat `protobuf:"bytes,7,opt,name=heartbeat,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*EventStreamResponse_EntityChanged) isEventStreamResponse_Event() {}
|
||||
|
||||
func (*EventStreamResponse_ConfigChanged) isEventStreamResponse_Event() {}
|
||||
|
|
@ -2639,6 +2653,8 @@ func (*EventStreamResponse_ExecutionStarted) isEventStreamResponse_Event() {}
|
|||
|
||||
func (*EventStreamResponse_OutputChunk) isEventStreamResponse_Event() {}
|
||||
|
||||
func (*EventStreamResponse_Heartbeat) isEventStreamResponse_Event() {}
|
||||
|
||||
type EventOutputChunk struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ExecutionTrackingId string `protobuf:"bytes,1,opt,name=execution_tracking_id,json=executionTrackingId,proto3" json:"execution_tracking_id,omitempty"`
|
||||
|
|
@ -2763,6 +2779,42 @@ func (*EventConfigChanged) Descriptor() ([]byte, []int) {
|
|||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{45}
|
||||
}
|
||||
|
||||
type EventHeartbeat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EventHeartbeat) Reset() {
|
||||
*x = EventHeartbeat{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[46]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EventHeartbeat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EventHeartbeat) ProtoMessage() {}
|
||||
|
||||
func (x *EventHeartbeat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[46]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EventHeartbeat.ProtoReflect.Descriptor instead.
|
||||
func (*EventHeartbeat) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{46}
|
||||
}
|
||||
|
||||
type EventExecutionFinished struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
LogEntry *LogEntry `protobuf:"bytes,1,opt,name=log_entry,json=logEntry,proto3" json:"log_entry,omitempty"`
|
||||
|
|
@ -2772,7 +2824,7 @@ type EventExecutionFinished struct {
|
|||
|
||||
func (x *EventExecutionFinished) Reset() {
|
||||
*x = EventExecutionFinished{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[46]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[47]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2784,7 +2836,7 @@ func (x *EventExecutionFinished) String() string {
|
|||
func (*EventExecutionFinished) ProtoMessage() {}
|
||||
|
||||
func (x *EventExecutionFinished) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[46]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[47]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2797,7 +2849,7 @@ func (x *EventExecutionFinished) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use EventExecutionFinished.ProtoReflect.Descriptor instead.
|
||||
func (*EventExecutionFinished) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{46}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{47}
|
||||
}
|
||||
|
||||
func (x *EventExecutionFinished) GetLogEntry() *LogEntry {
|
||||
|
|
@ -2816,7 +2868,7 @@ type EventExecutionStarted struct {
|
|||
|
||||
func (x *EventExecutionStarted) Reset() {
|
||||
*x = EventExecutionStarted{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[47]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[48]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2828,7 +2880,7 @@ func (x *EventExecutionStarted) String() string {
|
|||
func (*EventExecutionStarted) ProtoMessage() {}
|
||||
|
||||
func (x *EventExecutionStarted) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[47]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[48]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2841,7 +2893,7 @@ func (x *EventExecutionStarted) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use EventExecutionStarted.ProtoReflect.Descriptor instead.
|
||||
func (*EventExecutionStarted) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{47}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{48}
|
||||
}
|
||||
|
||||
func (x *EventExecutionStarted) GetLogEntry() *LogEntry {
|
||||
|
|
@ -2860,7 +2912,7 @@ type KillActionRequest struct {
|
|||
|
||||
func (x *KillActionRequest) Reset() {
|
||||
*x = KillActionRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[48]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[49]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2872,7 +2924,7 @@ func (x *KillActionRequest) String() string {
|
|||
func (*KillActionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *KillActionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[48]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[49]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2885,7 +2937,7 @@ func (x *KillActionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use KillActionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*KillActionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{48}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{49}
|
||||
}
|
||||
|
||||
func (x *KillActionRequest) GetExecutionTrackingId() string {
|
||||
|
|
@ -2907,7 +2959,7 @@ type KillActionResponse struct {
|
|||
|
||||
func (x *KillActionResponse) Reset() {
|
||||
*x = KillActionResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[49]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[50]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2919,7 +2971,7 @@ func (x *KillActionResponse) String() string {
|
|||
func (*KillActionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *KillActionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[49]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[50]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2932,7 +2984,7 @@ func (x *KillActionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use KillActionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*KillActionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{49}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{50}
|
||||
}
|
||||
|
||||
func (x *KillActionResponse) GetExecutionTrackingId() string {
|
||||
|
|
@ -2973,7 +3025,7 @@ type LocalUserLoginRequest struct {
|
|||
|
||||
func (x *LocalUserLoginRequest) Reset() {
|
||||
*x = LocalUserLoginRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[50]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[51]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2985,7 +3037,7 @@ func (x *LocalUserLoginRequest) String() string {
|
|||
func (*LocalUserLoginRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LocalUserLoginRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[50]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[51]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2998,7 +3050,7 @@ func (x *LocalUserLoginRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use LocalUserLoginRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LocalUserLoginRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{50}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{51}
|
||||
}
|
||||
|
||||
func (x *LocalUserLoginRequest) GetUsername() string {
|
||||
|
|
@ -3024,7 +3076,7 @@ type LocalUserLoginResponse struct {
|
|||
|
||||
func (x *LocalUserLoginResponse) Reset() {
|
||||
*x = LocalUserLoginResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[51]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[52]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3036,7 +3088,7 @@ func (x *LocalUserLoginResponse) String() string {
|
|||
func (*LocalUserLoginResponse) ProtoMessage() {}
|
||||
|
||||
func (x *LocalUserLoginResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[51]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[52]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3049,7 +3101,7 @@ func (x *LocalUserLoginResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use LocalUserLoginResponse.ProtoReflect.Descriptor instead.
|
||||
func (*LocalUserLoginResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{51}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{52}
|
||||
}
|
||||
|
||||
func (x *LocalUserLoginResponse) GetSuccess() bool {
|
||||
|
|
@ -3068,7 +3120,7 @@ type PasswordHashRequest struct {
|
|||
|
||||
func (x *PasswordHashRequest) Reset() {
|
||||
*x = PasswordHashRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[52]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[53]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3080,7 +3132,7 @@ func (x *PasswordHashRequest) String() string {
|
|||
func (*PasswordHashRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PasswordHashRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[52]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[53]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3093,7 +3145,7 @@ func (x *PasswordHashRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use PasswordHashRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PasswordHashRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{52}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{53}
|
||||
}
|
||||
|
||||
func (x *PasswordHashRequest) GetPassword() string {
|
||||
|
|
@ -3112,7 +3164,7 @@ type PasswordHashResponse struct {
|
|||
|
||||
func (x *PasswordHashResponse) Reset() {
|
||||
*x = PasswordHashResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[53]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[54]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3124,7 +3176,7 @@ func (x *PasswordHashResponse) String() string {
|
|||
func (*PasswordHashResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PasswordHashResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[53]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[54]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3137,7 +3189,7 @@ func (x *PasswordHashResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use PasswordHashResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PasswordHashResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{53}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{54}
|
||||
}
|
||||
|
||||
func (x *PasswordHashResponse) GetHash() string {
|
||||
|
|
@ -3155,7 +3207,7 @@ type LogoutRequest struct {
|
|||
|
||||
func (x *LogoutRequest) Reset() {
|
||||
*x = LogoutRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[54]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[55]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3167,7 +3219,7 @@ func (x *LogoutRequest) String() string {
|
|||
func (*LogoutRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LogoutRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[54]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[55]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3180,7 +3232,7 @@ func (x *LogoutRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LogoutRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{54}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{55}
|
||||
}
|
||||
|
||||
type LogoutResponse struct {
|
||||
|
|
@ -3191,7 +3243,7 @@ type LogoutResponse struct {
|
|||
|
||||
func (x *LogoutResponse) Reset() {
|
||||
*x = LogoutResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[55]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[56]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3203,7 +3255,7 @@ func (x *LogoutResponse) String() string {
|
|||
func (*LogoutResponse) ProtoMessage() {}
|
||||
|
||||
func (x *LogoutResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[55]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[56]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3216,7 +3268,7 @@ func (x *LogoutResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead.
|
||||
func (*LogoutResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{55}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{56}
|
||||
}
|
||||
|
||||
type GetDiagnosticsRequest struct {
|
||||
|
|
@ -3227,7 +3279,7 @@ type GetDiagnosticsRequest struct {
|
|||
|
||||
func (x *GetDiagnosticsRequest) Reset() {
|
||||
*x = GetDiagnosticsRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[56]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[57]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3239,7 +3291,7 @@ func (x *GetDiagnosticsRequest) String() string {
|
|||
func (*GetDiagnosticsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetDiagnosticsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[56]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[57]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3252,7 +3304,7 @@ func (x *GetDiagnosticsRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetDiagnosticsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetDiagnosticsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{56}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{57}
|
||||
}
|
||||
|
||||
type GetDiagnosticsResponse struct {
|
||||
|
|
@ -3265,7 +3317,7 @@ type GetDiagnosticsResponse struct {
|
|||
|
||||
func (x *GetDiagnosticsResponse) Reset() {
|
||||
*x = GetDiagnosticsResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[57]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[58]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3277,7 +3329,7 @@ func (x *GetDiagnosticsResponse) String() string {
|
|||
func (*GetDiagnosticsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetDiagnosticsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[57]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[58]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3290,7 +3342,7 @@ func (x *GetDiagnosticsResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetDiagnosticsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetDiagnosticsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{57}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{58}
|
||||
}
|
||||
|
||||
func (x *GetDiagnosticsResponse) GetSshFoundKey() string {
|
||||
|
|
@ -3315,7 +3367,7 @@ type InitRequest struct {
|
|||
|
||||
func (x *InitRequest) Reset() {
|
||||
*x = InitRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[58]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[59]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3327,7 +3379,7 @@ func (x *InitRequest) String() string {
|
|||
func (*InitRequest) ProtoMessage() {}
|
||||
|
||||
func (x *InitRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[58]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[59]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3340,7 +3392,7 @@ func (x *InitRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use InitRequest.ProtoReflect.Descriptor instead.
|
||||
func (*InitRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{58}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{59}
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
|
|
@ -3376,7 +3428,7 @@ type InitResponse struct {
|
|||
|
||||
func (x *InitResponse) Reset() {
|
||||
*x = InitResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[59]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[60]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3388,7 +3440,7 @@ func (x *InitResponse) String() string {
|
|||
func (*InitResponse) ProtoMessage() {}
|
||||
|
||||
func (x *InitResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[59]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[60]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3401,7 +3453,7 @@ func (x *InitResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use InitResponse.ProtoReflect.Descriptor instead.
|
||||
func (*InitResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{59}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{60}
|
||||
}
|
||||
|
||||
func (x *InitResponse) GetShowFooter() bool {
|
||||
|
|
@ -3589,7 +3641,7 @@ type AdditionalLink struct {
|
|||
|
||||
func (x *AdditionalLink) Reset() {
|
||||
*x = AdditionalLink{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[60]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[61]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3601,7 +3653,7 @@ func (x *AdditionalLink) String() string {
|
|||
func (*AdditionalLink) ProtoMessage() {}
|
||||
|
||||
func (x *AdditionalLink) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[60]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[61]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3614,7 +3666,7 @@ func (x *AdditionalLink) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use AdditionalLink.ProtoReflect.Descriptor instead.
|
||||
func (*AdditionalLink) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{60}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{61}
|
||||
}
|
||||
|
||||
func (x *AdditionalLink) GetTitle() string {
|
||||
|
|
@ -3642,7 +3694,7 @@ type OAuth2Provider struct {
|
|||
|
||||
func (x *OAuth2Provider) Reset() {
|
||||
*x = OAuth2Provider{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[61]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[62]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3654,7 +3706,7 @@ func (x *OAuth2Provider) String() string {
|
|||
func (*OAuth2Provider) ProtoMessage() {}
|
||||
|
||||
func (x *OAuth2Provider) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[61]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[62]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3667,7 +3719,7 @@ func (x *OAuth2Provider) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use OAuth2Provider.ProtoReflect.Descriptor instead.
|
||||
func (*OAuth2Provider) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{61}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{62}
|
||||
}
|
||||
|
||||
func (x *OAuth2Provider) GetTitle() string {
|
||||
|
|
@ -3700,7 +3752,7 @@ type GetActionBindingRequest struct {
|
|||
|
||||
func (x *GetActionBindingRequest) Reset() {
|
||||
*x = GetActionBindingRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[62]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[63]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3712,7 +3764,7 @@ func (x *GetActionBindingRequest) String() string {
|
|||
func (*GetActionBindingRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetActionBindingRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[62]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[63]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3725,7 +3777,7 @@ func (x *GetActionBindingRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetActionBindingRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetActionBindingRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{62}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{63}
|
||||
}
|
||||
|
||||
func (x *GetActionBindingRequest) GetBindingId() string {
|
||||
|
|
@ -3744,7 +3796,7 @@ type GetActionBindingResponse struct {
|
|||
|
||||
func (x *GetActionBindingResponse) Reset() {
|
||||
*x = GetActionBindingResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[63]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[64]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3756,7 +3808,7 @@ func (x *GetActionBindingResponse) String() string {
|
|||
func (*GetActionBindingResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetActionBindingResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[63]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[64]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3769,7 +3821,7 @@ func (x *GetActionBindingResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetActionBindingResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetActionBindingResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{63}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{64}
|
||||
}
|
||||
|
||||
func (x *GetActionBindingResponse) GetAction() *Action {
|
||||
|
|
@ -3787,7 +3839,7 @@ type GetEntitiesRequest struct {
|
|||
|
||||
func (x *GetEntitiesRequest) Reset() {
|
||||
*x = GetEntitiesRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[64]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[65]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3799,7 +3851,7 @@ func (x *GetEntitiesRequest) String() string {
|
|||
func (*GetEntitiesRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetEntitiesRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[64]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[65]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3812,7 +3864,7 @@ func (x *GetEntitiesRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetEntitiesRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetEntitiesRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{64}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{65}
|
||||
}
|
||||
|
||||
type GetEntitiesResponse struct {
|
||||
|
|
@ -3824,7 +3876,7 @@ type GetEntitiesResponse struct {
|
|||
|
||||
func (x *GetEntitiesResponse) Reset() {
|
||||
*x = GetEntitiesResponse{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[65]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[66]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3836,7 +3888,7 @@ func (x *GetEntitiesResponse) String() string {
|
|||
func (*GetEntitiesResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetEntitiesResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[65]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[66]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3849,7 +3901,7 @@ func (x *GetEntitiesResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetEntitiesResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetEntitiesResponse) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{65}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{66}
|
||||
}
|
||||
|
||||
func (x *GetEntitiesResponse) GetEntityDefinitions() []*EntityDefinition {
|
||||
|
|
@ -3870,7 +3922,7 @@ type EntityDefinition struct {
|
|||
|
||||
func (x *EntityDefinition) Reset() {
|
||||
*x = EntityDefinition{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[66]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[67]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3882,7 +3934,7 @@ func (x *EntityDefinition) String() string {
|
|||
func (*EntityDefinition) ProtoMessage() {}
|
||||
|
||||
func (x *EntityDefinition) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[66]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[67]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3895,7 +3947,7 @@ func (x *EntityDefinition) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use EntityDefinition.ProtoReflect.Descriptor instead.
|
||||
func (*EntityDefinition) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{66}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{67}
|
||||
}
|
||||
|
||||
func (x *EntityDefinition) GetTitle() string {
|
||||
|
|
@ -3929,7 +3981,7 @@ type GetEntityRequest struct {
|
|||
|
||||
func (x *GetEntityRequest) Reset() {
|
||||
*x = GetEntityRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[67]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[68]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3941,7 +3993,7 @@ func (x *GetEntityRequest) String() string {
|
|||
func (*GetEntityRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetEntityRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[67]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[68]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -3954,7 +4006,7 @@ func (x *GetEntityRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetEntityRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetEntityRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{67}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{68}
|
||||
}
|
||||
|
||||
func (x *GetEntityRequest) GetUniqueKey() string {
|
||||
|
|
@ -3980,7 +4032,7 @@ type RestartActionRequest struct {
|
|||
|
||||
func (x *RestartActionRequest) Reset() {
|
||||
*x = RestartActionRequest{}
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[68]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -3992,7 +4044,7 @@ func (x *RestartActionRequest) String() string {
|
|||
func (*RestartActionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RestartActionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[68]
|
||||
mi := &file_olivetin_api_v1_olivetin_proto_msgTypes[69]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -4005,7 +4057,7 @@ func (x *RestartActionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use RestartActionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RestartActionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{68}
|
||||
return file_olivetin_api_v1_olivetin_proto_rawDescGZIP(), []int{69}
|
||||
}
|
||||
|
||||
func (x *RestartActionRequest) GetExecutionTrackingId() string {
|
||||
|
|
@ -4220,19 +4272,21 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
|
|||
"\x10GetReadyzRequest\"+\n" +
|
||||
"\x11GetReadyzResponse\x12\x16\n" +
|
||||
"\x06status\x18\x01 \x01(\tR\x06status\"\x14\n" +
|
||||
"\x12EventStreamRequest\"\xb3\x03\n" +
|
||||
"\x12EventStreamRequest\"\xf4\x03\n" +
|
||||
"\x13EventStreamResponse\x12L\n" +
|
||||
"\x0eentity_changed\x18\x02 \x01(\v2#.olivetin.api.v1.EventEntityChangedH\x00R\rentityChanged\x12L\n" +
|
||||
"\x0econfig_changed\x18\x03 \x01(\v2#.olivetin.api.v1.EventConfigChangedH\x00R\rconfigChanged\x12X\n" +
|
||||
"\x12execution_finished\x18\x04 \x01(\v2'.olivetin.api.v1.EventExecutionFinishedH\x00R\x11executionFinished\x12U\n" +
|
||||
"\x11execution_started\x18\x05 \x01(\v2&.olivetin.api.v1.EventExecutionStartedH\x00R\x10executionStarted\x12F\n" +
|
||||
"\foutput_chunk\x18\x06 \x01(\v2!.olivetin.api.v1.EventOutputChunkH\x00R\voutputChunkB\a\n" +
|
||||
"\foutput_chunk\x18\x06 \x01(\v2!.olivetin.api.v1.EventOutputChunkH\x00R\voutputChunk\x12?\n" +
|
||||
"\theartbeat\x18\a \x01(\v2\x1f.olivetin.api.v1.EventHeartbeatH\x00R\theartbeatB\a\n" +
|
||||
"\x05event\"^\n" +
|
||||
"\x10EventOutputChunk\x122\n" +
|
||||
"\x15execution_tracking_id\x18\x01 \x01(\tR\x13executionTrackingId\x12\x16\n" +
|
||||
"\x06output\x18\x02 \x01(\tR\x06output\"\x14\n" +
|
||||
"\x12EventEntityChanged\"\x14\n" +
|
||||
"\x12EventConfigChanged\"P\n" +
|
||||
"\x12EventConfigChanged\"\x10\n" +
|
||||
"\x0eEventHeartbeat\"P\n" +
|
||||
"\x16EventExecutionFinished\x126\n" +
|
||||
"\tlog_entry\x18\x01 \x01(\v2\x19.olivetin.api.v1.LogEntryR\blogEntry\"O\n" +
|
||||
"\x15EventExecutionStarted\x126\n" +
|
||||
|
|
@ -4355,7 +4409,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, 75)
|
||||
var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 76)
|
||||
var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
||||
(*Action)(nil), // 0: olivetin.api.v1.Action
|
||||
(*ActionWebhookExecHint)(nil), // 1: olivetin.api.v1.ActionWebhookExecHint
|
||||
|
|
@ -4403,44 +4457,45 @@ var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
|||
(*EventOutputChunk)(nil), // 43: olivetin.api.v1.EventOutputChunk
|
||||
(*EventEntityChanged)(nil), // 44: olivetin.api.v1.EventEntityChanged
|
||||
(*EventConfigChanged)(nil), // 45: olivetin.api.v1.EventConfigChanged
|
||||
(*EventExecutionFinished)(nil), // 46: olivetin.api.v1.EventExecutionFinished
|
||||
(*EventExecutionStarted)(nil), // 47: olivetin.api.v1.EventExecutionStarted
|
||||
(*KillActionRequest)(nil), // 48: olivetin.api.v1.KillActionRequest
|
||||
(*KillActionResponse)(nil), // 49: olivetin.api.v1.KillActionResponse
|
||||
(*LocalUserLoginRequest)(nil), // 50: olivetin.api.v1.LocalUserLoginRequest
|
||||
(*LocalUserLoginResponse)(nil), // 51: olivetin.api.v1.LocalUserLoginResponse
|
||||
(*PasswordHashRequest)(nil), // 52: olivetin.api.v1.PasswordHashRequest
|
||||
(*PasswordHashResponse)(nil), // 53: olivetin.api.v1.PasswordHashResponse
|
||||
(*LogoutRequest)(nil), // 54: olivetin.api.v1.LogoutRequest
|
||||
(*LogoutResponse)(nil), // 55: olivetin.api.v1.LogoutResponse
|
||||
(*GetDiagnosticsRequest)(nil), // 56: olivetin.api.v1.GetDiagnosticsRequest
|
||||
(*GetDiagnosticsResponse)(nil), // 57: olivetin.api.v1.GetDiagnosticsResponse
|
||||
(*InitRequest)(nil), // 58: olivetin.api.v1.InitRequest
|
||||
(*InitResponse)(nil), // 59: olivetin.api.v1.InitResponse
|
||||
(*AdditionalLink)(nil), // 60: olivetin.api.v1.AdditionalLink
|
||||
(*OAuth2Provider)(nil), // 61: olivetin.api.v1.OAuth2Provider
|
||||
(*GetActionBindingRequest)(nil), // 62: olivetin.api.v1.GetActionBindingRequest
|
||||
(*GetActionBindingResponse)(nil), // 63: olivetin.api.v1.GetActionBindingResponse
|
||||
(*GetEntitiesRequest)(nil), // 64: olivetin.api.v1.GetEntitiesRequest
|
||||
(*GetEntitiesResponse)(nil), // 65: olivetin.api.v1.GetEntitiesResponse
|
||||
(*EntityDefinition)(nil), // 66: olivetin.api.v1.EntityDefinition
|
||||
(*GetEntityRequest)(nil), // 67: olivetin.api.v1.GetEntityRequest
|
||||
(*RestartActionRequest)(nil), // 68: olivetin.api.v1.RestartActionRequest
|
||||
nil, // 69: olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||
nil, // 70: olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||
nil, // 71: olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
nil, // 72: olivetin.api.v1.Entity.FieldsEntry
|
||||
nil, // 73: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
nil, // 74: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
(*EventHeartbeat)(nil), // 46: olivetin.api.v1.EventHeartbeat
|
||||
(*EventExecutionFinished)(nil), // 47: olivetin.api.v1.EventExecutionFinished
|
||||
(*EventExecutionStarted)(nil), // 48: olivetin.api.v1.EventExecutionStarted
|
||||
(*KillActionRequest)(nil), // 49: olivetin.api.v1.KillActionRequest
|
||||
(*KillActionResponse)(nil), // 50: olivetin.api.v1.KillActionResponse
|
||||
(*LocalUserLoginRequest)(nil), // 51: olivetin.api.v1.LocalUserLoginRequest
|
||||
(*LocalUserLoginResponse)(nil), // 52: olivetin.api.v1.LocalUserLoginResponse
|
||||
(*PasswordHashRequest)(nil), // 53: olivetin.api.v1.PasswordHashRequest
|
||||
(*PasswordHashResponse)(nil), // 54: olivetin.api.v1.PasswordHashResponse
|
||||
(*LogoutRequest)(nil), // 55: olivetin.api.v1.LogoutRequest
|
||||
(*LogoutResponse)(nil), // 56: olivetin.api.v1.LogoutResponse
|
||||
(*GetDiagnosticsRequest)(nil), // 57: olivetin.api.v1.GetDiagnosticsRequest
|
||||
(*GetDiagnosticsResponse)(nil), // 58: olivetin.api.v1.GetDiagnosticsResponse
|
||||
(*InitRequest)(nil), // 59: olivetin.api.v1.InitRequest
|
||||
(*InitResponse)(nil), // 60: olivetin.api.v1.InitResponse
|
||||
(*AdditionalLink)(nil), // 61: olivetin.api.v1.AdditionalLink
|
||||
(*OAuth2Provider)(nil), // 62: olivetin.api.v1.OAuth2Provider
|
||||
(*GetActionBindingRequest)(nil), // 63: olivetin.api.v1.GetActionBindingRequest
|
||||
(*GetActionBindingResponse)(nil), // 64: olivetin.api.v1.GetActionBindingResponse
|
||||
(*GetEntitiesRequest)(nil), // 65: olivetin.api.v1.GetEntitiesRequest
|
||||
(*GetEntitiesResponse)(nil), // 66: olivetin.api.v1.GetEntitiesResponse
|
||||
(*EntityDefinition)(nil), // 67: olivetin.api.v1.EntityDefinition
|
||||
(*GetEntityRequest)(nil), // 68: olivetin.api.v1.GetEntityRequest
|
||||
(*RestartActionRequest)(nil), // 69: olivetin.api.v1.RestartActionRequest
|
||||
nil, // 70: olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||
nil, // 71: olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||
nil, // 72: olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
nil, // 73: olivetin.api.v1.Entity.FieldsEntry
|
||||
nil, // 74: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
nil, // 75: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
}
|
||||
var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{
|
||||
2, // 0: olivetin.api.v1.Action.arguments:type_name -> olivetin.api.v1.ActionArgument
|
||||
1, // 1: olivetin.api.v1.Action.exec_on_webhooks:type_name -> olivetin.api.v1.ActionWebhookExecHint
|
||||
69, // 2: olivetin.api.v1.ActionWebhookExecHint.match_headers:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||
70, // 3: olivetin.api.v1.ActionWebhookExecHint.match_query:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||
70, // 2: olivetin.api.v1.ActionWebhookExecHint.match_headers:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||
71, // 3: olivetin.api.v1.ActionWebhookExecHint.match_query:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||
3, // 4: olivetin.api.v1.ActionArgument.choices:type_name -> olivetin.api.v1.ActionArgumentChoice
|
||||
71, // 5: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
72, // 6: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry
|
||||
72, // 5: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||
73, // 6: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry
|
||||
8, // 7: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard
|
||||
9, // 8: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
9, // 9: olivetin.api.v1.DashboardComponent.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||
|
|
@ -4452,77 +4507,78 @@ var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{
|
|||
20, // 15: olivetin.api.v1.GetLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
20, // 16: olivetin.api.v1.GetActionLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||
20, // 17: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
73, // 18: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
74, // 19: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
74, // 18: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||
75, // 19: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||
44, // 20: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged
|
||||
45, // 21: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged
|
||||
46, // 22: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
||||
47, // 23: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
||||
47, // 22: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
||||
48, // 23: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
||||
43, // 24: olivetin.api.v1.EventStreamResponse.output_chunk:type_name -> olivetin.api.v1.EventOutputChunk
|
||||
20, // 25: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
20, // 26: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
61, // 27: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
||||
60, // 28: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
||||
6, // 29: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
||||
0, // 30: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
||||
66, // 31: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
||||
4, // 32: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
||||
36, // 33: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding
|
||||
7, // 34: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
||||
10, // 35: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
||||
13, // 36: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
||||
15, // 37: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
||||
17, // 38: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
||||
68, // 39: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
||||
48, // 40: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
||||
28, // 41: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
||||
19, // 42: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
||||
22, // 43: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
||||
24, // 44: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
||||
30, // 45: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
||||
32, // 46: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
||||
34, // 47: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
||||
37, // 48: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
||||
39, // 49: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
||||
50, // 50: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
||||
52, // 51: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
||||
54, // 52: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
||||
41, // 53: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
||||
56, // 54: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
||||
58, // 55: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
||||
62, // 56: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
||||
64, // 57: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
||||
67, // 58: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
||||
5, // 59: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
||||
12, // 60: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
14, // 61: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
||||
16, // 62: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
||||
18, // 63: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
||||
12, // 64: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
49, // 65: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
||||
29, // 66: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
||||
21, // 67: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
||||
23, // 68: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
||||
25, // 69: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
||||
31, // 70: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
||||
33, // 71: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
||||
35, // 72: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
||||
38, // 73: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
||||
40, // 74: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
||||
51, // 75: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
||||
53, // 76: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
||||
55, // 77: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
||||
42, // 78: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
||||
57, // 79: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
||||
59, // 80: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
||||
63, // 81: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
||||
65, // 82: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
||||
4, // 83: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
||||
59, // [59:84] is the sub-list for method output_type
|
||||
34, // [34:59] is the sub-list for method input_type
|
||||
34, // [34:34] is the sub-list for extension type_name
|
||||
34, // [34:34] is the sub-list for extension extendee
|
||||
0, // [0:34] is the sub-list for field type_name
|
||||
46, // 25: olivetin.api.v1.EventStreamResponse.heartbeat:type_name -> olivetin.api.v1.EventHeartbeat
|
||||
20, // 26: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
20, // 27: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||
62, // 28: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
||||
61, // 29: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
||||
6, // 30: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
||||
0, // 31: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
||||
67, // 32: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
||||
4, // 33: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
||||
36, // 34: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding
|
||||
7, // 35: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
||||
10, // 36: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
||||
13, // 37: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
||||
15, // 38: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
||||
17, // 39: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
||||
69, // 40: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
||||
49, // 41: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
||||
28, // 42: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
||||
19, // 43: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
||||
22, // 44: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
||||
24, // 45: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
||||
30, // 46: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
||||
32, // 47: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
||||
34, // 48: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
||||
37, // 49: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
||||
39, // 50: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
||||
51, // 51: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
||||
53, // 52: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
||||
55, // 53: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
||||
41, // 54: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
||||
57, // 55: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
||||
59, // 56: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
||||
63, // 57: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
||||
65, // 58: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
||||
68, // 59: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
||||
5, // 60: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
||||
12, // 61: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
14, // 62: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
||||
16, // 63: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
||||
18, // 64: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
||||
12, // 65: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||
50, // 66: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
||||
29, // 67: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
||||
21, // 68: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
||||
23, // 69: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
||||
25, // 70: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
||||
31, // 71: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
||||
33, // 72: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
||||
35, // 73: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
||||
38, // 74: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
||||
40, // 75: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
||||
52, // 76: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
||||
54, // 77: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
||||
56, // 78: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
||||
42, // 79: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
||||
58, // 80: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
||||
60, // 81: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
||||
64, // 82: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
||||
66, // 83: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
||||
4, // 84: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
||||
60, // [60:85] is the sub-list for method output_type
|
||||
35, // [35:60] is the sub-list for method input_type
|
||||
35, // [35:35] is the sub-list for extension type_name
|
||||
35, // [35:35] is the sub-list for extension extendee
|
||||
0, // [0:35] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_olivetin_api_v1_olivetin_proto_init() }
|
||||
|
|
@ -4536,6 +4592,7 @@ func file_olivetin_api_v1_olivetin_proto_init() {
|
|||
(*EventStreamResponse_ExecutionFinished)(nil),
|
||||
(*EventStreamResponse_ExecutionStarted)(nil),
|
||||
(*EventStreamResponse_OutputChunk)(nil),
|
||||
(*EventStreamResponse_Heartbeat)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
|
|
@ -4543,7 +4600,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: 75,
|
||||
NumMessages: 76,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -58,18 +58,43 @@ func (api *oliveTinAPI) copyOfStreamingClients() []*streamingClient {
|
|||
type streamingClient struct {
|
||||
channel chan *apiv1.EventStreamResponse
|
||||
AuthenticatedUser *authpublic.AuthenticatedUser
|
||||
heartbeatStopOnce sync.Once
|
||||
heartbeatStop chan struct{}
|
||||
heartbeatDone chan struct{}
|
||||
}
|
||||
|
||||
// trySendEventToClient sends msg to the client's channel. Returns false if the channel is full (client should be removed).
|
||||
func (c *streamingClient) stopHeartbeat() {
|
||||
if c.heartbeatStop == nil || c.heartbeatDone == nil {
|
||||
return
|
||||
}
|
||||
c.heartbeatStopOnce.Do(func() {
|
||||
close(c.heartbeatStop)
|
||||
})
|
||||
<-c.heartbeatDone
|
||||
}
|
||||
|
||||
// trySendEventToClient sends msg to the client's channel. Returns false if the channel is full or closed.
|
||||
func (api *oliveTinAPI) trySendEventToClient(client *streamingClient, msg *apiv1.EventStreamResponse) bool {
|
||||
if client == nil || msg == nil {
|
||||
return false
|
||||
}
|
||||
sent := sendToStreamingClientChannel(client.channel, msg)
|
||||
if !sent {
|
||||
log.Warnf("EventStream: client channel is full or closed, removing client")
|
||||
}
|
||||
return sent
|
||||
}
|
||||
|
||||
func sendToStreamingClientChannel(ch chan *apiv1.EventStreamResponse, msg *apiv1.EventStreamResponse) (sent bool) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
sent = false
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case client.channel <- msg:
|
||||
case ch <- msg:
|
||||
return true
|
||||
default:
|
||||
log.Warnf("EventStream: client channel is full, removing client")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -933,6 +958,8 @@ func (api *oliveTinAPI) EventStream(ctx ctx.Context, req *connect.Request[apiv1.
|
|||
client := &streamingClient{
|
||||
channel: make(chan *apiv1.EventStreamResponse, 10), // Buffered channel to hold Events
|
||||
AuthenticatedUser: user,
|
||||
heartbeatStop: make(chan struct{}),
|
||||
heartbeatDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
|
|
@ -943,6 +970,8 @@ func (api *oliveTinAPI) EventStream(ctx ctx.Context, req *connect.Request[apiv1.
|
|||
api.streamingClients[client] = struct{}{}
|
||||
api.streamingClientsMutex.Unlock()
|
||||
|
||||
go api.sendEventStreamHeartbeats(client)
|
||||
|
||||
// loop over client channel and send events to connectedClient
|
||||
for msg := range client.channel {
|
||||
log.Debugf("Sending event to client: %v", msg)
|
||||
|
|
@ -959,13 +988,59 @@ func (api *oliveTinAPI) EventStream(ctx ctx.Context, req *connect.Request[apiv1.
|
|||
return nil
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) sendEventStreamHeartbeats(client *streamingClient) {
|
||||
defer close(client.heartbeatDone)
|
||||
|
||||
if !api.sendEventStreamHeartbeat(client) {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
api.runEventStreamHeartbeatLoop(client, ticker)
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) runEventStreamHeartbeatLoop(client *streamingClient, ticker *time.Ticker) {
|
||||
for {
|
||||
if api.waitEventStreamHeartbeatOrDone(client.heartbeatStop, ticker) {
|
||||
return
|
||||
}
|
||||
if !api.sendEventStreamHeartbeat(client) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) waitEventStreamHeartbeatOrDone(done <-chan struct{}, ticker *time.Ticker) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return true
|
||||
case <-ticker.C:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) sendEventStreamHeartbeat(client *streamingClient) bool {
|
||||
msg := &apiv1.EventStreamResponse{
|
||||
Event: &apiv1.EventStreamResponse_Heartbeat{
|
||||
Heartbeat: &apiv1.EventHeartbeat{},
|
||||
},
|
||||
}
|
||||
return api.trySendEventToClient(client, msg)
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) removeClient(clientToRemove *streamingClient) {
|
||||
if clientToRemove == nil {
|
||||
return
|
||||
}
|
||||
api.streamingClientsMutex.Lock()
|
||||
if _, exists := api.streamingClients[clientToRemove]; !exists {
|
||||
api.streamingClientsMutex.Unlock()
|
||||
return
|
||||
}
|
||||
delete(api.streamingClients, clientToRemove)
|
||||
api.streamingClientsMutex.Unlock()
|
||||
clientToRemove.stopHeartbeat()
|
||||
close(clientToRemove.channel)
|
||||
}
|
||||
|
||||
|
|
@ -973,14 +1048,12 @@ func (api *oliveTinAPI) OnActionMapRebuilt() {
|
|||
toRemove := []*streamingClient{}
|
||||
|
||||
for _, client := range api.copyOfStreamingClients() {
|
||||
select {
|
||||
case client.channel <- &apiv1.EventStreamResponse{
|
||||
msg := &apiv1.EventStreamResponse{
|
||||
Event: &apiv1.EventStreamResponse_ConfigChanged{
|
||||
ConfigChanged: &apiv1.EventConfigChanged{},
|
||||
},
|
||||
}:
|
||||
default:
|
||||
log.Warnf("EventStream: client channel is full, removing client")
|
||||
}
|
||||
if !api.trySendEventToClient(client, msg) {
|
||||
toRemove = append(toRemove, client)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue