mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
dd0889e3d6
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.
424 lines
23 KiB
HTML
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>
|
|
|
|
`
|
|
}) %>
|