Add per-node action groups with schedules, runs, and logs.
Let operators manage ordered action groups on each host, run them manually or on a schedule over SSH, and inspect disk-backed JSON run logs from a node detail UI.
This commit is contained in:
+45
-2
@@ -73,6 +73,7 @@ import {
|
||||
type LogsPageSize,
|
||||
} from './logsPreferences'
|
||||
import { LoginPage } from './LoginPage'
|
||||
import { NodeDetailPanel } from './NodeDetailPanel'
|
||||
import {
|
||||
countAuthFailuresToday,
|
||||
countNodesBySection,
|
||||
@@ -86,6 +87,7 @@ import {
|
||||
pathFor,
|
||||
subscribeToLocation,
|
||||
type ConfigTabId,
|
||||
type NodeDetailTabId,
|
||||
type ResourceSectionId,
|
||||
type ResourceTabId,
|
||||
type SectionId,
|
||||
@@ -2546,8 +2548,21 @@ function ResourceOverviewTab({
|
||||
return (
|
||||
<li key={node.id} className="node-list-item">
|
||||
<div className="node-list-heading">
|
||||
<strong>{node.name}</strong>
|
||||
<code className="node-id">{node.id}</code>
|
||||
<div className="node-list-heading-main">
|
||||
<strong>{node.name}</strong>
|
||||
<code className="node-id">{node.id}</code>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="node-edit"
|
||||
onClick={() => {
|
||||
navigateTo(
|
||||
pathFor(section, 'overview', 'overview', node.id, 'actions'),
|
||||
)
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p className="config-hint">
|
||||
{node.host_ip} · {node.username} · group {node.group_name} ·{' '}
|
||||
@@ -2919,10 +2934,14 @@ function ResourceSectionPanel({
|
||||
section,
|
||||
activeResourceTab,
|
||||
onResourceTabChange,
|
||||
nodeId,
|
||||
nodeDetailTab,
|
||||
}: {
|
||||
section: ResourceSectionId
|
||||
activeResourceTab: ResourceTabId
|
||||
onResourceTabChange: (tab: ResourceTabId) => void
|
||||
nodeId: string | null
|
||||
nodeDetailTab: NodeDetailTabId
|
||||
}) {
|
||||
const [me, setMe] = useState<MeResponse | null>(null)
|
||||
const [overviewKey, setOverviewKey] = useState(0)
|
||||
@@ -2955,6 +2974,16 @@ function ResourceSectionPanel({
|
||||
const canDelete = Boolean(me?.permissions?.includes('nodes.delete'))
|
||||
const tabs = resourceTabsFor(section)
|
||||
|
||||
if (nodeId) {
|
||||
return (
|
||||
<NodeDetailPanel
|
||||
section={section}
|
||||
nodeId={nodeId}
|
||||
activeTab={nodeDetailTab}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="config-panel">
|
||||
<div
|
||||
@@ -3743,6 +3772,11 @@ function App() {
|
||||
const [activeResourceTab, setActiveResourceTab] = useState<ResourceTabId>(
|
||||
initialLocation.resourceTab,
|
||||
)
|
||||
const [activeNodeId, setActiveNodeId] = useState<string | null>(
|
||||
initialLocation.nodeId,
|
||||
)
|
||||
const [activeNodeDetailTab, setActiveNodeDetailTab] =
|
||||
useState<NodeDetailTabId>(initialLocation.nodeDetailTab)
|
||||
const [profileDisplayName, setProfileDisplayName] = useState<string | null>(
|
||||
null,
|
||||
)
|
||||
@@ -3764,6 +3798,8 @@ function App() {
|
||||
setActiveSection(location.section)
|
||||
setActiveConfigTab(location.configTab)
|
||||
setActiveResourceTab(location.resourceTab)
|
||||
setActiveNodeId(location.nodeId)
|
||||
setActiveNodeDetailTab(location.nodeDetailTab)
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -3916,6 +3952,8 @@ function App() {
|
||||
return
|
||||
}
|
||||
setActiveSection(section)
|
||||
setActiveNodeId(null)
|
||||
setActiveNodeDetailTab('overview')
|
||||
if (section === 'configuration') {
|
||||
navigateTo(pathFor(section, activeConfigTab))
|
||||
return
|
||||
@@ -3932,6 +3970,7 @@ function App() {
|
||||
function goToConfigTab(tab: ConfigTabId) {
|
||||
setActiveSection('configuration')
|
||||
setActiveConfigTab(tab)
|
||||
setActiveNodeId(null)
|
||||
navigateTo(pathFor('configuration', tab))
|
||||
}
|
||||
|
||||
@@ -3940,6 +3979,8 @@ function App() {
|
||||
return
|
||||
}
|
||||
setActiveResourceTab(tab)
|
||||
setActiveNodeId(null)
|
||||
setActiveNodeDetailTab('overview')
|
||||
navigateTo(pathFor(activeSection, 'overview', tab))
|
||||
}
|
||||
|
||||
@@ -4150,6 +4191,8 @@ function App() {
|
||||
section={activeSection}
|
||||
activeResourceTab={activeResourceTab}
|
||||
onResourceTabChange={goToResourceTab}
|
||||
nodeId={activeNodeId}
|
||||
nodeDetailTab={activeNodeDetailTab}
|
||||
/>
|
||||
) : (
|
||||
<p>Coming soon</p>
|
||||
|
||||
Reference in New Issue
Block a user