Implementing more advanced feature: audit and more
This commit is contained in:
+107
-5
@@ -403,6 +403,7 @@ function DiagramSignalTree() {
|
||||
|
||||
const SECTIONS = [
|
||||
{ id: 'start', label: 'Getting Started' },
|
||||
{ id: 'tips', label: 'Quick Tips' },
|
||||
{ id: 'view', label: 'View Mode' },
|
||||
{ id: 'edit', label: 'Edit Mode' },
|
||||
{ id: 'widgets', label: 'Widgets' },
|
||||
@@ -411,10 +412,68 @@ const SECTIONS = [
|
||||
{ id: 'logic', label: 'Panel Logic' },
|
||||
{ id: 'control', label: 'Control Logic' },
|
||||
{ id: 'sharing', label: 'Sharing & Access' },
|
||||
{ id: 'audit', label: 'Audit Log' },
|
||||
{ id: 'history', label: 'Historical Data' },
|
||||
{ id: 'shortcuts', label: 'Keyboard Shortcuts' },
|
||||
];
|
||||
|
||||
// Quick tips, grouped by the mode they apply to. Each links to the manual
|
||||
// section that covers it in depth. (Previously surfaced via a floating
|
||||
// contextual-help popover; now consolidated here in the manual.)
|
||||
const TIPS: Record<'view' | 'edit', Array<{ text: string; section: string }>> = {
|
||||
view: [
|
||||
{ text: 'Click any interface in the left panel to load it on the canvas.', section: 'view' },
|
||||
{ text: 'Right-click a widget to view signal info or export data as CSV.', section: 'view' },
|
||||
{ text: 'The ● Live button shows live streaming is active. Use the date pickers to load historical data.', section: 'history' },
|
||||
{ text: 'Click Edit (top-right) to open the drag-and-drop panel builder.', section: 'edit' },
|
||||
{ text: 'Use the Share button on a panel to grant access to users or groups.', section: 'sharing' },
|
||||
{ text: 'The ⚙ Control logic button opens server-side automation that runs even with no panel open.', section: 'control' },
|
||||
{ text: 'The 🛡 Audit button (for permitted users) opens the log of system-affecting actions.', section: 'audit' },
|
||||
{ text: 'The status chip turns green and shows your username when the WebSocket is connected.', section: 'view' },
|
||||
],
|
||||
edit: [
|
||||
{ text: 'Drag a signal from the left tree onto the canvas to create a widget.', section: 'edit' },
|
||||
{ text: 'Click a widget to select it, then adjust its properties on the right.', section: 'edit' },
|
||||
{ text: 'Ctrl+click to select multiple widgets; then use the align toolbar.', section: 'edit' },
|
||||
{ text: 'Ctrl+Z to undo, Ctrl+Y to redo. Up to 50 steps are remembered.', section: 'shortcuts' },
|
||||
{ text: 'Use "+ Synthetic" in the signal tree to create computed/filtered signals.', section: 'signals' },
|
||||
{ text: 'Switch to the Logic tab to add interactive behaviour with a node-graph flow editor.', section: 'logic' },
|
||||
{ text: 'Add Local variables for set-points and counters that live inside the panel.', section: 'edit' },
|
||||
{ text: 'Arrow keys nudge the selected widget by 1 px (Shift for grid size).', section: 'shortcuts' },
|
||||
],
|
||||
};
|
||||
|
||||
function SectionTips({ onNavigate }: { onNavigate: (section: string) => void }) {
|
||||
const groups: Array<['view' | 'edit', string]> = [
|
||||
['view', 'In View mode'],
|
||||
['edit', 'In Edit mode'],
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<p class="help-lead">
|
||||
A handful of quick pointers to get the most out of uopi. Click any tip to jump to the
|
||||
section that explains it in detail.
|
||||
</p>
|
||||
{groups.map(([mode, title]) => (
|
||||
<div key={mode}>
|
||||
<h3 class="help-h3">{title}</h3>
|
||||
<ul class="help-tips-list">
|
||||
{TIPS[mode].map(tip => (
|
||||
<li key={tip.text}>
|
||||
<button class="help-tip-item" onClick={() => onNavigate(tip.section)}>
|
||||
<span class="help-tip-icon">💡</span>
|
||||
<span class="help-tip-text">{tip.text}</span>
|
||||
<span class="help-tip-go">↗</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionStart() {
|
||||
return (
|
||||
<div>
|
||||
@@ -481,8 +540,10 @@ function SectionView() {
|
||||
|
||||
<h3 class="help-h3">Connection status</h3>
|
||||
<p>
|
||||
The coloured chip in the top-right shows the WebSocket connection state.
|
||||
A red <em>"disconnected"</em> banner appears at the top while reconnecting — live values will resume automatically.
|
||||
The coloured chip in the top-right shows the WebSocket connection state. When connected it
|
||||
reads <em>"connected as <your username>"</em>, so you can confirm the identity your
|
||||
actions are attributed to in the audit log. A red <em>"disconnected"</em> banner appears at
|
||||
the top while reconnecting — live values will resume automatically.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -677,6 +738,44 @@ function SectionSharing() {
|
||||
);
|
||||
}
|
||||
|
||||
function SectionAudit() {
|
||||
return (
|
||||
<div>
|
||||
<p class="help-lead">
|
||||
When the server is configured with <code>[audit] enabled = true</code>, every
|
||||
<strong> system-affecting action</strong> is appended to an append-only log so it can be
|
||||
reviewed later. Permitted users open it with the <strong>🛡 Audit</strong> button in the
|
||||
view-mode toolbar.
|
||||
</p>
|
||||
|
||||
<h3 class="help-h3">What gets recorded</h3>
|
||||
<ul class="help-list">
|
||||
<li><strong>Signal writes</strong> — both user-initiated (set-value widgets, buttons, panel
|
||||
logic) and automated writes from the control-logic engine.</li>
|
||||
<li><strong>Interface changes</strong> — panels created, updated, or deleted.</li>
|
||||
<li><strong>Control-logic changes</strong> — graphs created, updated, or deleted.</li>
|
||||
</ul>
|
||||
<p>Each entry records the time, the actor (username, or the graph name for automated
|
||||
actions), the action, the affected data source / signal and value, the client address, and
|
||||
whether it succeeded.</p>
|
||||
|
||||
<h3 class="help-h3">Viewing the log</h3>
|
||||
<ul class="help-list">
|
||||
<li>The viewer lists the most recent events first.</li>
|
||||
<li>Filter by user, action, signal, or a start/end time range.</li>
|
||||
<li>System (automated) actions are tagged distinctly from user actions.</li>
|
||||
</ul>
|
||||
|
||||
<p class="help-callout">
|
||||
Access to the audit log is gated by an allowlist (<code>audit.readers</code> in the config),
|
||||
following the same model as logic editing: an empty list means every user may view it,
|
||||
otherwise only the listed users or groups can. The 🛡 Audit button is hidden for users who
|
||||
are not permitted.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionWidgets() {
|
||||
return (
|
||||
<div>
|
||||
@@ -858,8 +957,9 @@ function SectionShortcuts() {
|
||||
);
|
||||
}
|
||||
|
||||
const SECTION_COMPONENTS: Record<string, () => JSX.Element> = {
|
||||
const SECTION_COMPONENTS: Record<string, (props: { onNavigate: (section: string) => void }) => JSX.Element> = {
|
||||
start: SectionStart,
|
||||
tips: SectionTips,
|
||||
view: SectionView,
|
||||
edit: SectionEdit,
|
||||
widgets: SectionWidgets,
|
||||
@@ -868,6 +968,7 @@ const SECTION_COMPONENTS: Record<string, () => JSX.Element> = {
|
||||
logic: SectionLogic,
|
||||
control: SectionControl,
|
||||
sharing: SectionSharing,
|
||||
audit: SectionAudit,
|
||||
history: SectionHistory,
|
||||
shortcuts: SectionShortcuts,
|
||||
};
|
||||
@@ -877,7 +978,8 @@ const SECTION_COMPONENTS: Record<string, () => JSX.Element> = {
|
||||
export default function HelpModal({ initialSection = 'start', onClose }: Props) {
|
||||
const [active, setActive] = useState(initialSection);
|
||||
|
||||
const Content = SECTION_COMPONENTS[active] ?? SectionStart;
|
||||
const Content: (props: { onNavigate: (section: string) => void }) => JSX.Element =
|
||||
SECTION_COMPONENTS[active] ?? SectionStart;
|
||||
|
||||
// Close on backdrop click
|
||||
function handleBackdrop(e: MouseEvent) {
|
||||
@@ -915,7 +1017,7 @@ export default function HelpModal({ initialSection = 'start', onClose }: Props)
|
||||
{/* Content area */}
|
||||
<div class="help-content">
|
||||
<h2 class="help-h2">{SECTIONS.find(s => s.id === active)?.label}</h2>
|
||||
<Content />
|
||||
<Content onNavigate={setActive} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user