| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767 |
- import './ActionButton.js' // To define action-button
- import { NavigationBar } from './NavigationBar.js'
- import { ExecutionDialog } from './ExecutionDialog.js'
- import { ActionStatusDisplay } from './ActionStatusDisplay.js'
- function getQueryParams () {
- return new URLSearchParams(window.location.search.substring(1))
- }
- function checkAndTriggerActionFromQueryParam () {
- const params = getQueryParams()
- const action = params.get('action')
- if (action && window.actionButtons) {
- // Look for an action button with matching title
- const actionButton = window.actionButtons[action]
- if (actionButton) {
- // Only trigger actions that have arguments
- const jsonButton = window.actionButtonsJson[action]
- if (jsonButton && jsonButton.arguments && jsonButton.arguments.length > 0) {
- // Trigger the action button click
- setTimeout(() => {
- actionButton.btn.click()
- }, 500) // Small delay to ensure UI is fully loaded
- return true
- }
- }
- }
- return false
- }
- function createElement (tag, attributes) {
- const el = document.createElement(tag)
- if (attributes !== null) {
- if (attributes.classNames !== undefined) {
- el.classList.add(...attributes.classNames)
- }
- if (attributes.innerText !== undefined) {
- el.innerText = attributes.innerText
- }
- }
- return el
- }
- function createTag (val) {
- const domTag = createElement('span', {
- innerText: val,
- classNames: ['tag']
- })
- return domTag
- }
- function createAnnotation (key, val) {
- const domAnnotation = createElement('span', {
- classNames: ['annotation']
- })
- domAnnotation.appendChild(createElement('span', {
- innerText: key,
- classNames: ['annotation-key']
- }))
- domAnnotation.appendChild(createElement('span', {
- innerText: val,
- classNames: ['annotation-value']
- }))
- return domAnnotation
- }
- /**
- * This is a weird function that just sets some globals.
- */
- export function initMarshaller () {
- window.navbar = new NavigationBar()
- window.showSection = showSection
- window.showSectionView = showSectionView
- window.executionDialog = new ExecutionDialog()
- window.logEntries = new Map()
- window.registeredPaths = new Map()
- window.breadcrumbNavigation = []
- window.currentPath = ''
- window.addEventListener('EventExecutionStarted', onExecutionStarted)
- window.addEventListener('EventExecutionFinished', onExecutionFinished)
- window.addEventListener('EventOutputChunk', onOutputChunk)
- }
- function setUsername (username, provider) {
- document.getElementById('username').innerText = username
- document.getElementById('username').setAttribute('title', provider)
- if (window.settings.AuthLocalLogin || window.settings.AuthOAuth2Providers !== null) {
- if (username === 'guest') {
- document.getElementById('link-login').hidden = false
- document.getElementById('link-logout').hidden = true
- } else {
- document.getElementById('link-login').hidden = true
- if (provider === 'local' || provider === 'oauth2') {
- document.getElementById('link-logout').hidden = false
- }
- }
- }
- }
- export function marshalDashboardComponentsJsonToHtml (json) {
- if (json == null) { // eg: HTTP 403
- setUsername('guest', 'system')
- if (window.settings.AuthLoginUrl !== '') {
- window.location = window.settings.AuthLoginUrl
- } else {
- showSection('/login')
- }
- } else {
- setUsername(json.authenticatedUser, json.authenticatedUserProvider)
- marshalActionsJsonToHtml(json)
- marshalDashboardStructureToHtml(json)
- window.navbar.refreshSectionPolicyLinks(json.effectivePolicy)
- refreshDiagnostics(json)
- }
- document.body.setAttribute('initial-marshal-complete', 'true')
- }
- function marshalActionsJsonToHtml (json) {
- const currentIterationTimestamp = Date.now()
- window.actionButtons = {}
- window.actionButtonsJson = {} // Store the JSON representation
- for (const jsonButton of json.actions) {
- let htmlButton = window.actionButtons[jsonButton.id]
- if (typeof htmlButton === 'undefined') {
- htmlButton = document.createElement('action-button')
- htmlButton.constructFromJson(jsonButton)
- window.actionButtons[jsonButton.title] = htmlButton
- window.actionButtonsJson[jsonButton.title] = jsonButton // Store the JSON representation
- }
- htmlButton.updateFromJson(jsonButton)
- htmlButton.updateIterationTimestamp = currentIterationTimestamp
- }
- // Remove existing, but stale buttons (that were not updated in this round)
- for (const existingButton of document.querySelectorAll('action-button')) {
- if (existingButton.updateIterationTimestamp !== currentIterationTimestamp) {
- existingButton.remove()
- }
- }
- }
- function onOutputChunk (evt) {
- const chunk = evt.payload
- if (chunk.executionTrackingId === window.executionDialog.executionTrackingId) {
- window.terminal.write(chunk.output)
- window.executionDialog.showOutput()
- }
- }
- function onExecutionStarted (evt) {
- const logEntry = evt.payload.logEntry
- marshalLogsJsonToHtml({
- logs: [logEntry]
- })
- }
- function onExecutionFinished (evt) {
- const logEntry = evt.payload.logEntry
- window.logEntries.set(logEntry.executionTrackingId, logEntry)
- const actionButton = window.actionButtons[logEntry.actionTitle]
- if (actionButton === undefined) {
- return
- }
- const executionButton = document.querySelector('execution-button#execution-' + logEntry.executionTrackingId)
- let feedbackButton = actionButton
- switch (actionButton.popupOnStart) {
- case 'execution-button':
- if (executionButton != null) {
- feedbackButton = executionButton
- }
- break
- case 'execution-dialog-output-html':
- case 'execution-dialog-stdout-only':
- case 'execution-dialog':
- // We don't need to fetch the logEntry for the dialog because we already
- // have it, so we open the dialog and it will get updated below.
- window.executionDialog.show()
- window.executionDialog.executionTrackingId = logEntry.uuid
- break
- }
- feedbackButton.onExecutionFinished(logEntry)
- marshalLogsJsonToHtml({
- logs: [logEntry]
- })
- // If the current execution dialog is open, update that too
- if (window.executionDialog.dlg.open && window.executionDialog.executionUuid === logEntry.uuid) {
- window.executionDialog.renderExecutionResult({
- logEntry: logEntry,
- type: actionButton.popupOnStart
- })
- }
- }
- function convertPathToBreadcrumb (path) {
- const parts = path.split('/')
- const result = []
- for (let i = 0; i < parts.length; i++) {
- if (parts[i] === '') {
- continue
- }
- result.push(parts.slice(0, i + 1).join('/'))
- }
- return result
- }
- function showExecutionResult (pathName) {
- const executionTrackingId = pathName.split('/')[2]
- window.executionDialog.fetchExecutionResult(executionTrackingId)
- window.executionDialog.show()
- }
- function showSection (pathName) {
- if (pathName.startsWith('/logs/')) {
- showExecutionResult(pathName)
- pushNewNavigationPath(pathName)
- return
- }
- const path = window.registeredPaths.get(pathName)
- if (path === undefined) {
- console.warn('Section not found by path: ' + pathName)
- showSection('/')
- return
- }
- window.convertPathToBreadcrumb = convertPathToBreadcrumb
- window.currentPath = pathName
- window.breadcrumbNavigation = convertPathToBreadcrumb(pathName)
- for (const section of document.querySelectorAll('section')) {
- if (section.title === path.section) {
- section.style.display = 'block'
- } else {
- section.style.display = 'none'
- }
- }
- // Check for action parameter in query string
- if (!checkAndTriggerActionFromQueryParam()) {
- pushNewNavigationPath(pathName)
- }
- setSectionNavigationVisible(false)
- showSectionView(path.view)
- }
- function pushNewNavigationPath (pathName) {
- // Get the current query string
- const queryString = window.location.search
- // Push the new state with the path and preserve the query string
- window.history.pushState({
- path: pathName
- }, null, pathName + queryString)
- }
- function setSectionNavigationVisible (visible) {
- const nav = document.querySelector('nav')
- const btn = document.getElementById('sidebar-toggler-button')
- nav.removeAttribute('hidden')
- if (document.body.classList.contains('has-sidebar')) {
- if (visible) {
- btn.setAttribute('aria-pressed', false)
- btn.setAttribute('aria-label', 'Open sidebar navigation')
- btn.innerHTML = '«'
- nav.classList.add('shown')
- } else {
- btn.setAttribute('aria-pressed', true)
- btn.setAttribute('aria-label', 'Close sidebar navigation')
- btn.innerHTML = '☰'
- nav.classList.remove('shown')
- }
- } else {
- btn.disabled = true
- }
- }
- export function setupSectionNavigation (style) {
- const nav = document.querySelector('nav')
- const btn = document.getElementById('sidebar-toggler-button')
- if (style === 'sidebar') {
- nav.classList.add('sidebar')
- document.body.classList.add('has-sidebar')
- btn.onclick = () => {
- if (nav.classList.contains('shown')) {
- setSectionNavigationVisible(false)
- } else {
- setSectionNavigationVisible(true)
- }
- }
- } else {
- nav.classList.add('topbar')
- document.body.classList.add('has-topbar')
- }
- registerSection('/', 'Actions', null, document.getElementById('showActions'))
- registerSection('/diagnostics', 'Diagnostics', null, null)
- registerSection('/logs', 'Logs', null, null)
- registerSection('/login', 'Login', null, null)
- }
- function registerSection (path, section, view, linkElement) {
- window.registeredPaths.set(path, {
- section: section,
- view: view
- })
- if (linkElement != null) {
- addLinkToSection(path, linkElement)
- }
- }
- function addLinkToSection (pathName, element) {
- const path = window.registeredPaths.get(pathName)
- element.href = 'javascript:void(0)'
- element.title = path.section
- element.onclick = () => {
- showSection(pathName)
- }
- }
- function refreshDiagnostics (json) {
- document.getElementById('diagnostics-sshfoundkey').innerHTML = json.diagnostics.SshFoundKey
- document.getElementById('diagnostics-sshfoundconfig').innerHTML = json.diagnostics.SshFoundConfig
- }
- function getSystemTitle (title) {
- return title.replaceAll(' ', '')
- }
- function marshalSingleDashboard (dashboard, nav) {
- const oldsection = document.querySelector('section[title="' + getSystemTitle(dashboard.title) + '"]')
- if (oldsection != null) {
- oldsection.remove()
- }
- const section = document.createElement('section')
- section.setAttribute('system-title', getSystemTitle(dashboard.title))
- section.title = section.getAttribute('system-title')
- const def = createFieldset('default', section)
- section.appendChild(def)
- document.getElementsByTagName('main')[0].appendChild(section)
- marshalContainerContents(dashboard, section, def, dashboard.title)
- const oldLi = nav.querySelector('li[title="' + dashboard.title + '"]')
- if (oldLi != null) {
- oldLi.remove()
- }
- const systemTitleUrl = '/' + getSystemTitle(dashboard.title)
- window.navbar.createLink(dashboard.title, systemTitleUrl, false)
- registerSection(systemTitleUrl, section.title, null, null)
- }
- function marshalDashboardStructureToHtml (json) {
- const nav = document.getElementById('navigation-links')
- for (const dashboard of json.dashboards) {
- marshalSingleDashboard(dashboard, nav)
- }
- const rootGroup = document.querySelector('#root-group')
- for (const btn of Object.values(window.actionButtons)) {
- if (btn.parentElement === null) {
- rootGroup.appendChild(btn)
- }
- }
- const shouldHideActions = rootGroup.querySelectorAll('action-button').length === 0 && json.dashboards.length > 0
- if (shouldHideActions) {
- nav.querySelector('li[title="Actions"]').style.display = 'none'
- }
- if (window.currentPath !== '') {
- showSection(window.currentPath)
- } else if (window.location.pathname !== '/' && document.body.getAttribute('initial-marshal-complete') === null) {
- showSection(window.location.pathname)
- } else {
- if (shouldHideActions) {
- showSection('/' + getSystemTitle(json.dashboards[0].title))
- } else {
- showSection('/')
- }
- }
- }
- function marshalLink (item, fieldset) {
- let btn = window.actionButtons[item.title]
- if (typeof btn === 'undefined') {
- btn = document.createElement('button')
- btn.innerText = 'Action not found: ' + item.title
- btn.classList.add('error')
- }
- if (item.cssClass !== '') {
- btn.classList.add(item.cssClass)
- }
- fieldset.appendChild(btn)
- }
- function marshalMreOutput (dashboardComponent, fieldset) {
- const pre = document.createElement('pre')
- pre.classList.add('mre-output')
- pre.innerHTML = 'Waiting...'
- const executionStatus = {
- actionId: dashboardComponent.title
- }
- window.fetch(window.restBaseUrl + 'ExecutionStatus', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(executionStatus)
- }).then((res) => {
- if (res.ok) {
- return res.json()
- } else {
- pre.innerHTML = 'error'
- throw new Error(res.statusText)
- }
- }).then((json) => {
- updateMre(pre, json.logEntry)
- })
- const updateMre = (pre, json) => {
- pre.innerHTML = json.output
- }
- window.addEventListener('ExecutionFinished', (e) => {
- // The dashboard component "title" field is used for lots of things
- // and in this context for MreOutput it's just to refer an an actionId.
- //
- // So this is not a typo.
- if (e.payload.actionId === dashboardComponent.title) {
- updateMre(pre, e.payload)
- }
- })
- fieldset.appendChild(pre)
- }
- function marshalContainerContents (json, section, fieldset, parentDashboard) {
- for (const item of json.contents) {
- switch (item.type) {
- case 'fieldset':
- marshalFieldset(item, section, parentDashboard)
- break
- case 'directory': {
- const directoryPath = marshalDirectory(item, section)
- marshalDirectoryButton(item, fieldset, directoryPath)
- }
- break
- case 'display':
- marshalDisplay(item, fieldset)
- break
- case 'stdout-most-recent-execution':
- marshalMreOutput(item, fieldset)
- break
- case 'link':
- marshalLink(item, fieldset)
- break
- default:
- }
- }
- }
- function createFieldset (title, parentDashboard) {
- const legend = document.createElement('legend')
- legend.innerText = title
- const fs = document.createElement('fieldset')
- fs.title = title
- fs.appendChild(legend)
- if (typeof parentDashboard === 'undefined') {
- fs.setAttribute('parent-dashboard', '')
- } else {
- fs.setAttribute('parent-dashboard', parentDashboard)
- }
- return fs
- }
- function marshalFieldset (item, section, parentDashboard) {
- const fs = createFieldset(item.title, parentDashboard)
- marshalContainerContents(item, section, fs, parentDashboard)
- section.appendChild(fs)
- }
- function showSectionView (selected) {
- if (selected === '') {
- selected = null
- }
- for (const fieldset of document.querySelectorAll('fieldset')) {
- if (selected === null) {
- if ((fieldset.id === 'root-group' || fieldset.getAttribute('parent-dashboard') !== '') && fieldset.children.length > 1) {
- fieldset.style.display = 'grid'
- } else {
- fieldset.style.display = 'none'
- }
- } else {
- if (fieldset.title === selected) {
- fieldset.style.display = 'grid'
- } else {
- fieldset.style.display = 'none'
- }
- }
- }
- const current = window.registeredPaths.get(window.currentPath)
- for (const navLink of document.querySelector('nav').querySelectorAll('a')) {
- if (navLink.title === current.section) {
- navLink.classList.add('selected')
- } else {
- navLink.classList.remove('selected')
- }
- }
- rebuildH1BreadcrumbNavigation(selected)
- pushNewNavigationPath(window.currentPath)
- }
- function rebuildH1BreadcrumbNavigation () {
- const title = document.querySelector('h1')
- title.innerHTML = ''
- const rootLink = document.createElement('a')
- rootLink.innerText = window.pageTitle
- rootLink.href = 'javascript:void(0)'
- rootLink.onclick = () => {
- showSection('/')
- }
- title.appendChild(rootLink)
- for (const pathName of window.breadcrumbNavigation) {
- const sep = document.createElement('span')
- sep.innerHTML = ' » '
- title.append(sep)
- const path = window.registeredPaths.get(pathName)
- title.appendChild(createNavigationBreadcrumbDisplay(path))
- }
- document.title = title.innerText
- }
- function createNavigationBreadcrumbDisplay (path) {
- const a = document.createElement('a')
- a.href = 'javascript:void(0)'
- if (path.view === null) {
- a.title = path.section
- a.innerText = path.section
- } else {
- a.innerText = path.view
- a.title = path.view
- }
- a.onclick = () => {
- showSectionView(path.view)
- }
- return a
- }
- function marshalDisplay (item, fieldset) {
- const display = document.createElement('div')
- display.innerHTML = item.title
- display.classList.add('display')
- if (item.cssClass !== '') {
- display.classList.add(item.cssClass)
- }
- fieldset.appendChild(display)
- }
- function marshalDirectoryButton (item, fieldset, path) {
- const directoryButton = document.createElement('button')
- directoryButton.innerHTML = '<span class = "icon">' + item.icon + '</span> ' + item.title
- directoryButton.onclick = () => {
- showSection(path)
- }
- fieldset.appendChild(directoryButton)
- }
- function marshalDirectory (item, section) {
- const fs = createFieldset(item.title)
- fs.style.display = 'none'
- const directoryBackButton = document.createElement('button')
- directoryBackButton.innerHTML = window.settings.DefaultIconForBack
- directoryBackButton.title = 'Go back one directory'
- directoryBackButton.onclick = () => {
- showSection('/' + section.title)
- }
- fs.appendChild(directoryBackButton)
- marshalContainerContents(item, section, fs)
- section.appendChild(fs)
- const path = '/' + section.title + '/' + getSystemTitle(item.title)
- registerSection(path, section.title, item.title, null)
- return path
- }
- export function marshalLogsJsonToHtml (json) {
- // This function is called internally with a "fake" server response, that does
- // not have pageSize set. So we need to check if it's set before trying to use it.
- if (json.pageSize !== undefined) {
- document.getElementById('logs-server-page-size').innerText = json.pageSize
- }
- if (json.logs != null && json.logs.length > 0) {
- document.getElementById('logsTable').hidden = false
- document.getElementById('logsTableEmpty').hidden = true
- } else {
- return
- }
- for (const logEntry of json.logs) {
- let row = document.getElementById('log-' + logEntry.executionTrackingId)
- if (row == null) {
- const tpl = document.getElementById('tplLogRow')
- row = tpl.content.querySelector('tr').cloneNode(true)
- row.id = 'log-' + logEntry.executionTrackingId
- row.querySelector('.content').onclick = () => {
- window.executionDialog.reset()
- window.executionDialog.show()
- window.executionDialog.renderExecutionResult({
- logEntry: window.logEntries.get(logEntry.executionTrackingId)
- })
- pushNewNavigationPath('/logs/' + logEntry.executionTrackingId)
- }
- row.exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code'))
- logEntry.dom = row
- window.logEntries.set(logEntry.executionTrackingId, logEntry)
- document.querySelector('#logTableBody').prepend(row)
- }
- row.querySelector('.timestamp').innerText = logEntry.datetimeStarted
- row.querySelector('.content').innerText = logEntry.actionTitle
- row.querySelector('.icon').innerHTML = logEntry.actionIcon
- row.setAttribute('title', logEntry.actionTitle)
- row.exitCodeDisplay.update(logEntry)
- row.querySelector('.tags').innerHTML = ''
- for (const tag of logEntry.tags) {
- row.querySelector('.tags').append(createTag(tag))
- }
- row.querySelector('.tags').append(createAnnotation('user', logEntry.user))
- }
- }
- window.addEventListener('popstate', (e) => {
- e.preventDefault()
- if (e.state != null && typeof e.state.path !== 'undefined') {
- showSection(e.state.path)
- }
- })
- export function refreshServerConnectionLabel () {
- if (window.restAvailable) {
- document.querySelector('#serverConnectionRest').classList.remove('error')
- } else {
- document.querySelector('#serverConnectionRest').classList.add('error')
- }
- if (window.websocketAvailable) {
- document.querySelector('#serverConnectionWebSocket').classList.remove('error')
- document.querySelector('#serverConnectionWebSocket').innerText = 'WebSocket'
- } else {
- document.querySelector('#serverConnectionWebSocket').classList.add('error')
- document.querySelector('#serverConnectionWebSocket').innerText = 'WebSocket Error'
- }
- }
|