Files
UNITRONIX dd0889e3d6 Add SessionManager; update docs, i18n & CI
Introduce a SessionManager for relay-based remote sessions in the Tauri MGMT client: new SessionCommand API, start/stop/session input routing, clipboard/recording/quality controls, and notification read/dismiss state. Wire AppState with new mutexes and show main window on startup. CI: add SBOM generation (anchore) and Trivy vulnerability scan steps. Misc: change console Docker DB path, large README/CHANGELOG updates (chat E2E, unattended access/WOL, i18n expansion, CDAP/SDK docs), and many web-nodejs assets/locales/routes/views/services and server-side changes.
2026-04-05 13:42:07 +02:00

424 lines
23 KiB
HTML

<%- include('layouts/main', {
title: title,
pageStyles: pageStyles,
pageScripts: pageScripts,
currentPage: currentPage,
breadcrumb: breadcrumb,
body: `
<div class="fleet-page">
<!-- Tab navigation -->
<div class="fleet-tabs">
<button class="fleet-tab ${activeTab === 'resources' ? 'active' : ''}" data-tab="resources" onclick="Fleet.switchTab('resources')">
<span class="material-icons">dns</span>
<span>${ typeof req !== 'undefined' ? req.t('fleet.tab_resources') : 'Resources' }</span>
</button>
<button class="fleet-tab ${activeTab === 'tasks' ? 'active' : ''}" data-tab="tasks" onclick="Fleet.switchTab('tasks')">
<span class="material-icons">schedule_send</span>
<span>${ typeof req !== 'undefined' ? req.t('fleet.tab_tasks') : 'Tasks' }</span>
</button>
<button class="fleet-tab ${activeTab === 'builder' ? 'active' : ''}" data-tab="builder" onclick="Fleet.switchTab('builder')">
<span class="material-icons">account_tree</span>
<span>${ typeof req !== 'undefined' ? req.t('fleet.tab_builder') : 'Workflow Builder' }</span>
</button>
<button class="fleet-tab ${activeTab === 'compliance' ? 'active' : ''}" data-tab="compliance" onclick="Fleet.switchTab('compliance')">
<span class="material-icons">verified_user</span>
<span>${ typeof req !== 'undefined' ? req.t('fleet.tab_compliance') : 'Compliance' }</span>
</button>
</div>
<!-- ======= RESOURCES TAB ======= -->
<div class="fleet-panel" id="panel-resources" style="display: ${activeTab === 'resources' ? 'block' : 'none'};">
<div class="fleet-toolbar">
<div class="fleet-toolbar-left">
<select id="res-org-select" class="fleet-select" onchange="Fleet.onResOrgChange()">
<option value="">` + _('fleet.select_org_placeholder') + `</option>
</select>
<select id="res-type-filter" class="fleet-select" onchange="Fleet.loadResources()">
<option value="">` + _('fleet.all_types') + `</option>
<option value="printer">` + _('fleet.type_printer') + `</option>
<option value="network_drive">` + _('fleet.type_network_drive') + `</option>
<option value="dns">` + _('fleet.type_dns') + `</option>
<option value="vpn">` + _('fleet.type_vpn') + `</option>
<option value="proxy">` + _('fleet.type_proxy') + `</option>
</select>
</div>
<button class="btn btn-primary btn-sm" onclick="Fleet.showResourceModal()">
<span class="material-icons">add</span> ` + _('fleet.add_resource') + `
</button>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table">
<thead>
<tr>
<th>` + _('fleet.col_name') + `</th>
<th>` + _('fleet.col_type') + `</th>
<th>` + _('fleet.col_target') + `</th>
<th>` + _('fleet.col_scope') + `</th>
<th>` + _('fleet.col_status') + `</th>
<th>` + _('fleet.col_actions') + `</th>
</tr>
</thead>
<tbody id="res-tbody"></tbody>
</table>
<div class="fleet-empty" id="res-empty" style="display:none;">
<span class="material-icons">inbox</span>
<p>` + _('fleet.no_resources') + `</p>
</div>
</div>
</div>
<!-- ======= TASKS TAB ======= -->
<div class="fleet-panel" id="panel-tasks" style="display: ${activeTab === 'tasks' ? 'block' : 'none'};">
<div class="fleet-toolbar">
<div class="fleet-toolbar-left">
<select id="task-status-filter" class="fleet-select" onchange="Fleet.loadTasks()">
<option value="">` + _('fleet.all_statuses') + `</option>
<option value="pending">` + _('fleet.status_pending') + `</option>
<option value="running">` + _('fleet.status_running') + `</option>
<option value="success">` + _('fleet.status_success') + `</option>
<option value="failed">` + _('fleet.status_failed') + `</option>
</select>
<input type="text" class="fleet-search" id="task-search" placeholder="` + _('fleet.search_tasks') + `" onkeyup="Fleet.filterTasks()">
</div>
<button class="btn btn-primary btn-sm" onclick="Fleet.showTaskModal()">
<span class="material-icons">add</span> ` + _('fleet.create_task') + `
</button>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table">
<thead>
<tr>
<th>` + _('fleet.col_task_name') + `</th>
<th>` + _('fleet.col_task_type') + `</th>
<th>` + _('fleet.col_target') + `</th>
<th>` + _('fleet.col_schedule') + `</th>
<th>` + _('fleet.col_status') + `</th>
<th>` + _('fleet.col_last_run') + `</th>
<th>` + _('fleet.col_actions') + `</th>
</tr>
</thead>
<tbody id="task-tbody"></tbody>
</table>
<div class="fleet-empty" id="task-empty" style="display:none;">
<span class="material-icons">inbox</span>
<p>` + _('fleet.no_tasks') + `</p>
</div>
</div>
<!-- Task output panel (slide-up) -->
<div class="fleet-output-panel" id="task-output-panel" style="display:none;">
<div class="fleet-output-header">
<span class="material-icons">terminal</span>
<span id="task-output-title">` + _('fleet.task_output') + `</span>
<button class="btn-icon" onclick="Fleet.closeOutput()"><span class="material-icons">close</span></button>
</div>
<pre class="fleet-output-pre" id="task-output-pre"></pre>
</div>
</div>
<!-- ======= BUILDER TAB ======= -->
<div class="fleet-panel" id="panel-builder" style="display: ${activeTab === 'builder' ? 'block' : 'none'};">
<div class="fleet-toolbar">
<div class="fleet-toolbar-left">
<select id="workflow-select" class="fleet-select" onchange="Fleet.loadWorkflow()">
<option value="">` + _('fleet.select_workflow') + `</option>
</select>
</div>
<div class="fleet-toolbar-right">
<button class="btn btn-secondary btn-sm" onclick="Fleet.newWorkflow()">
<span class="material-icons">add</span> ` + _('fleet.new_workflow') + `
</button>
<button class="btn btn-secondary btn-sm" onclick="Fleet.saveWorkflow()">
<span class="material-icons">save</span> ` + _('fleet.save') + `
</button>
<button class="btn btn-secondary btn-sm" onclick="Fleet.exportWorkflow()">
<span class="material-icons">file_download</span> ` + _('fleet.export') + `
</button>
<button class="btn btn-primary btn-sm" onclick="Fleet.executeWorkflow(false)">
<span class="material-icons">play_arrow</span> ` + _('fleet.execute') + `
</button>
<button class="btn btn-outline btn-sm" onclick="Fleet.executeWorkflow(true)">
<span class="material-icons">science</span> ` + _('fleet.dry_run') + `
</button>
</div>
</div>
<!-- Node palette -->
<div class="builder-layout">
<div class="builder-palette">
<div class="palette-section" data-category="trigger">
<h4><span class="node-dot trigger"></span> ` + _('fleet.node_triggers') + `</h4>
<div class="palette-node" draggable="true" data-type="trigger_manual">
<span class="material-icons">touch_app</span> ` + _('fleet.node_manual') + `
</div>
<div class="palette-node" draggable="true" data-type="trigger_schedule">
<span class="material-icons">schedule</span> ` + _('fleet.node_schedule') + `
</div>
<div class="palette-node" draggable="true" data-type="trigger_event">
<span class="material-icons">bolt</span> ` + _('fleet.node_event') + `
</div>
</div>
<div class="palette-section" data-category="condition">
<h4><span class="node-dot condition"></span> ` + _('fleet.node_conditions') + `</h4>
<div class="palette-node" draggable="true" data-type="cond_os">
<span class="material-icons">computer</span> ` + _('fleet.node_os_check') + `
</div>
<div class="palette-node" draggable="true" data-type="cond_threshold">
<span class="material-icons">speed</span> ` + _('fleet.node_threshold') + `
</div>
<div class="palette-node" draggable="true" data-type="cond_time_window">
<span class="material-icons">access_time</span> ` + _('fleet.node_time_window') + `
</div>
</div>
<div class="palette-section" data-category="action">
<h4><span class="node-dot action"></span> ` + _('fleet.node_actions') + `</h4>
<div class="palette-node" draggable="true" data-type="action_script">
<span class="material-icons">code</span> ` + _('fleet.node_run_script') + `
</div>
<div class="palette-node" draggable="true" data-type="action_install">
<span class="material-icons">download</span> ` + _('fleet.node_install') + `
</div>
<div class="palette-node" draggable="true" data-type="action_restart">
<span class="material-icons">restart_alt</span> ` + _('fleet.node_restart') + `
</div>
<div class="palette-node" draggable="true" data-type="action_file_deploy">
<span class="material-icons">file_copy</span> ` + _('fleet.node_file_deploy') + `
</div>
<div class="palette-node" draggable="true" data-type="action_message">
<span class="material-icons">message</span> ` + _('fleet.node_send_message') + `
</div>
</div>
<div class="palette-section" data-category="result">
<h4><span class="node-dot result"></span> ` + _('fleet.node_results') + `</h4>
<div class="palette-node" draggable="true" data-type="result_log">
<span class="material-icons">receipt_long</span> ` + _('fleet.node_log_output') + `
</div>
<div class="palette-node" draggable="true" data-type="result_alert">
<span class="material-icons">notifications_active</span> ` + _('fleet.node_send_alert') + `
</div>
<div class="palette-node" draggable="true" data-type="result_chain">
<span class="material-icons">link</span> ` + _('fleet.node_chain') + `
</div>
</div>
<div class="palette-section" data-category="transform">
<h4><span class="node-dot transform"></span> ` + _('fleet.node_transforms') + `</h4>
<div class="palette-node" draggable="true" data-type="transform_parse">
<span class="material-icons">data_object</span> ` + _('fleet.node_parse_json') + `
</div>
<div class="palette-node" draggable="true" data-type="transform_filter">
<span class="material-icons">filter_alt</span> ` + _('fleet.node_filter') + `
</div>
<div class="palette-node" draggable="true" data-type="transform_delay">
<span class="material-icons">hourglass_empty</span> ` + _('fleet.node_delay') + `
</div>
</div>
</div>
<!-- Canvas -->
<div class="builder-canvas-wrap">
<svg id="builder-canvas" class="builder-canvas"></svg>
<div class="builder-zoom">
<button class="btn-icon" onclick="FleetBuilder.zoomIn()"><span class="material-icons">zoom_in</span></button>
<span id="builder-zoom-level">100%</span>
<button class="btn-icon" onclick="FleetBuilder.zoomOut()"><span class="material-icons">zoom_out</span></button>
<button class="btn-icon" onclick="FleetBuilder.zoomReset()"><span class="material-icons">fit_screen</span></button>
</div>
</div>
<!-- Node properties -->
<div class="builder-properties" id="builder-props" style="display:none;">
<div class="props-header">
<span id="props-title">` + _('fleet.node_properties') + `</span>
<button class="btn-icon" onclick="FleetBuilder.closeProps()"><span class="material-icons">close</span></button>
</div>
<div id="props-body"></div>
</div>
</div>
</div>
<!-- ======= COMPLIANCE TAB ======= -->
<div class="fleet-panel" id="panel-compliance" style="display: ${activeTab === 'compliance' ? 'block' : 'none'};">
<div class="fleet-toolbar">
<div class="fleet-toolbar-left">
<select id="comp-org-select" class="fleet-select" onchange="Fleet.loadCompliance()">
<option value="">` + _('fleet.all_organizations') + `</option>
</select>
</div>
<button class="btn btn-secondary btn-sm" onclick="Fleet.scanAll()">
<span class="material-icons">radar</span> ` + _('fleet.scan_all') + `
</button>
</div>
<!-- Compliance stats -->
<div class="compliance-stats" id="compliance-stats">
<div class="comp-stat-card compliant">
<span class="material-icons">check_circle</span>
<div class="comp-stat-body">
<span class="comp-stat-value" id="comp-compliant">0</span>
<span class="comp-stat-label">` + _('fleet.compliant') + `</span>
</div>
</div>
<div class="comp-stat-card warning">
<span class="material-icons">warning</span>
<div class="comp-stat-body">
<span class="comp-stat-value" id="comp-warning">0</span>
<span class="comp-stat-label">` + _('fleet.non_compliant') + `</span>
</div>
</div>
<div class="comp-stat-card critical">
<span class="material-icons">gpp_bad</span>
<div class="comp-stat-body">
<span class="comp-stat-value" id="comp-critical">0</span>
<span class="comp-stat-label">` + _('fleet.critical_issues') + `</span>
</div>
</div>
<div class="comp-stat-card score">
<span class="material-icons">speed</span>
<div class="comp-stat-body">
<span class="comp-stat-value" id="comp-score">--%</span>
<span class="comp-stat-label">` + _('fleet.avg_score') + `</span>
</div>
</div>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table">
<thead>
<tr>
<th>` + _('fleet.col_device') + `</th>
<th>` + _('fleet.col_hostname') + `</th>
<th>` + _('fleet.col_os') + `</th>
<th>` + _('fleet.col_score') + `</th>
<th>` + _('fleet.col_issues') + `</th>
<th>` + _('fleet.col_last_scan') + `</th>
<th>` + _('fleet.col_actions') + `</th>
</tr>
</thead>
<tbody id="comp-tbody"></tbody>
</table>
<div class="fleet-empty" id="comp-empty" style="display:none;">
<span class="material-icons">inbox</span>
<p>` + _('fleet.no_compliance_data') + `</p>
</div>
</div>
</div>
</div>
<!-- Resource modal -->
<div class="modal-overlay" id="resource-modal" style="display:none;">
<div class="modal-dialog">
<div class="modal-header">
<h3 id="res-modal-title">` + _('fleet.add_resource') + `</h3>
<button class="btn-icon" onclick="Fleet.closeResourceModal()"><span class="material-icons">close</span></button>
</div>
<div class="modal-body">
<input type="hidden" id="res-edit-id">
<div class="form-group">
<label>` + _('fleet.col_name') + `</label>
<input type="text" id="res-name" class="form-control">
</div>
<div class="form-group">
<label>` + _('fleet.col_type') + `</label>
<select id="res-type" class="form-control" onchange="Fleet.onResTypeChange()">
<option value="printer">` + _('fleet.type_printer') + `</option>
<option value="network_drive">` + _('fleet.type_network_drive') + `</option>
<option value="dns">` + _('fleet.type_dns') + `</option>
<option value="vpn">` + _('fleet.type_vpn') + `</option>
<option value="proxy">` + _('fleet.type_proxy') + `</option>
</select>
</div>
<div class="form-group" id="res-address-group">
<label id="res-address-label">` + _('fleet.address') + `</label>
<input type="text" id="res-address" class="form-control" placeholder="e.g. \\\\\\\\server\\\\printer">
</div>
<div class="form-group">
<label>` + _('fleet.col_scope') + `</label>
<select id="res-scope" class="form-control">
<option value="org">` + _('fleet.scope_org') + `</option>
<option value="group">` + _('fleet.scope_group') + `</option>
<option value="device">` + _('fleet.scope_device') + `</option>
</select>
</div>
<div class="form-group">
<label>` + _('fleet.scope_value') + `</label>
<input type="text" id="res-scope-value" class="form-control" placeholder="` + _('fleet.scope_value_hint') + `">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="Fleet.closeResourceModal()">` + _('fleet.cancel') + `</button>
<button class="btn btn-primary" onclick="Fleet.saveResource()">` + _('fleet.save') + `</button>
</div>
</div>
</div>
<!-- Task modal -->
<div class="modal-overlay" id="task-modal" style="display:none;">
<div class="modal-dialog modal-lg">
<div class="modal-header">
<h3 id="task-modal-title">` + _('fleet.create_task') + `</h3>
<button class="btn-icon" onclick="Fleet.closeTaskModal()"><span class="material-icons">close</span></button>
</div>
<div class="modal-body">
<input type="hidden" id="task-edit-id">
<div class="form-row">
<div class="form-group flex-1">
<label>` + _('fleet.col_task_name') + `</label>
<input type="text" id="task-name" class="form-control">
</div>
<div class="form-group flex-1">
<label>` + _('fleet.col_task_type') + `</label>
<select id="task-type" class="form-control">
<option value="script">` + _('fleet.task_type_script') + `</option>
<option value="restart">` + _('fleet.task_type_restart') + `</option>
<option value="update">` + _('fleet.task_type_update') + `</option>
<option value="install">` + _('fleet.task_type_install') + `</option>
<option value="file_deploy">` + _('fleet.task_type_file_deploy') + `</option>
<option value="registry">` + _('fleet.task_type_registry') + `</option>
</select>
</div>
</div>
<div class="form-group">
<label>` + _('fleet.target_devices') + `</label>
<input type="text" id="task-targets" class="form-control" placeholder="` + _('fleet.target_hint') + `">
</div>
<div class="form-group">
<label>` + _('fleet.col_schedule') + `</label>
<div class="form-row">
<select id="task-schedule-type" class="form-control flex-1" onchange="Fleet.onScheduleTypeChange()">
<option value="immediate">` + _('fleet.schedule_immediate') + `</option>
<option value="once">` + _('fleet.schedule_once') + `</option>
<option value="cron">` + _('fleet.schedule_cron') + `</option>
</select>
<input type="text" id="task-schedule-value" class="form-control flex-1" style="display:none;" placeholder="` + _('fleet.cron_placeholder') + `">
</div>
</div>
<div class="form-group">
<label>` + _('fleet.script_content') + `</label>
<textarea id="task-script" class="form-control code-textarea" rows="8" placeholder="#!/bin/bash or PowerShell"></textarea>
</div>
<div class="form-row">
<div class="form-group flex-1">
<label>` + _('fleet.retry_count') + `</label>
<input type="number" id="task-retries" class="form-control" value="0" min="0" max="3">
</div>
<div class="form-group flex-1">
<label>` + _('fleet.timeout_sec') + `</label>
<input type="number" id="task-timeout" class="form-control" value="300" min="30" max="3600">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="Fleet.closeTaskModal()">` + _('fleet.cancel') + `</button>
<button class="btn btn-primary" onclick="Fleet.saveTask()">` + _('fleet.save') + `</button>
</div>
</div>
</div>
`
}) %>