462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
// ui/pilotWindow.js - Fenêtre principale de l'extension Pilot Control
|
|
|
|
import GObject from 'gi://GObject';
|
|
import Gtk from 'gi://Gtk';
|
|
import Adw from 'gi://Adw';
|
|
import GLib from 'gi://GLib';
|
|
|
|
import {MetricEditDialog} from './metricEditDialog.js';
|
|
import {CommandEditDialog} from './commandEditDialog.js';
|
|
|
|
/**
|
|
* Fenêtre principale avec les sections Services, Telemetry, Commands
|
|
*/
|
|
export const PilotWindow = GObject.registerClass(
|
|
class PilotWindow extends Adw.Window {
|
|
_init(extension, yamlConfig, serviceManager) {
|
|
super._init({
|
|
title: 'Pilot Control Panel',
|
|
default_width: 800,
|
|
default_height: 600,
|
|
});
|
|
|
|
this._extension = extension;
|
|
this._yamlConfig = yamlConfig;
|
|
this._serviceManager = serviceManager;
|
|
|
|
this._buildUI();
|
|
this._loadData();
|
|
}
|
|
|
|
/**
|
|
* Construit l'interface utilisateur
|
|
*/
|
|
_buildUI() {
|
|
// Header bar
|
|
const headerBar = new Adw.HeaderBar();
|
|
|
|
// Bouton refresh
|
|
const refreshButton = new Gtk.Button({
|
|
icon_name: 'view-refresh-symbolic',
|
|
tooltip_text: 'Reload configuration',
|
|
});
|
|
refreshButton.connect('clicked', () => {
|
|
this._loadData();
|
|
});
|
|
headerBar.pack_end(refreshButton);
|
|
|
|
// Bouton save
|
|
const saveButton = new Gtk.Button({
|
|
icon_name: 'document-save-symbolic',
|
|
tooltip_text: 'Save configuration',
|
|
});
|
|
saveButton.connect('clicked', () => {
|
|
this._saveConfig();
|
|
});
|
|
headerBar.pack_end(saveButton);
|
|
|
|
// Toolbar view (GNOME 45+)
|
|
const toolbarView = new Adw.ToolbarView();
|
|
toolbarView.add_top_bar(headerBar);
|
|
|
|
// Main content box
|
|
const mainBox = new Gtk.Box({
|
|
orientation: Gtk.Orientation.VERTICAL,
|
|
margin_top: 12,
|
|
margin_bottom: 12,
|
|
margin_start: 12,
|
|
margin_end: 12,
|
|
spacing: 12,
|
|
});
|
|
|
|
// Scrolled window
|
|
const scrolledWindow = new Gtk.ScrolledWindow({
|
|
vexpand: true,
|
|
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
|
});
|
|
scrolledWindow.set_child(mainBox);
|
|
|
|
toolbarView.set_content(scrolledWindow);
|
|
this.set_content(toolbarView);
|
|
|
|
// Section: Service Control
|
|
mainBox.append(this._buildServiceSection());
|
|
|
|
// Section: Telemetry Metrics
|
|
mainBox.append(this._buildTelemetrySection());
|
|
|
|
// Section: Commands
|
|
mainBox.append(this._buildCommandsSection());
|
|
}
|
|
|
|
/**
|
|
* Construit la section Service Control
|
|
*/
|
|
_buildServiceSection() {
|
|
const group = new Adw.PreferencesGroup({
|
|
title: 'Service Control',
|
|
description: 'Manage the Pilot systemd service',
|
|
});
|
|
|
|
// Service status row
|
|
this._serviceStatusRow = new Adw.ActionRow({
|
|
title: 'Service Status',
|
|
subtitle: 'Unknown',
|
|
});
|
|
|
|
const serviceSwitch = new Gtk.Switch({
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
serviceSwitch.connect('notify::active', (sw) => {
|
|
if (sw.active) {
|
|
this._serviceManager.startService();
|
|
} else {
|
|
this._serviceManager.stopService();
|
|
}
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
|
|
this._updateServiceStatus();
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
});
|
|
this._serviceSwitch = serviceSwitch;
|
|
|
|
this._serviceStatusRow.add_suffix(serviceSwitch);
|
|
this._serviceStatusRow.activatable_widget = serviceSwitch;
|
|
|
|
group.add(this._serviceStatusRow);
|
|
|
|
// Service auto-start row
|
|
this._serviceEnableRow = new Adw.ActionRow({
|
|
title: 'Auto-start Service',
|
|
subtitle: 'Enable service at system startup',
|
|
});
|
|
|
|
const enableSwitch = new Gtk.Switch({
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
enableSwitch.connect('notify::active', (sw) => {
|
|
if (sw.active) {
|
|
this._serviceManager.enableService();
|
|
} else {
|
|
this._serviceManager.disableService();
|
|
}
|
|
});
|
|
this._serviceEnableSwitch = enableSwitch;
|
|
|
|
this._serviceEnableRow.add_suffix(enableSwitch);
|
|
this._serviceEnableRow.activatable_widget = enableSwitch;
|
|
|
|
group.add(this._serviceEnableRow);
|
|
|
|
// Restart button row
|
|
const restartRow = new Adw.ActionRow({
|
|
title: 'Restart Service',
|
|
subtitle: 'Apply configuration changes',
|
|
});
|
|
|
|
const restartButton = new Gtk.Button({
|
|
label: 'Restart',
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
restartButton.connect('clicked', () => {
|
|
this._serviceManager.restartService();
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
|
|
this._updateServiceStatus();
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
});
|
|
|
|
restartRow.add_suffix(restartButton);
|
|
group.add(restartRow);
|
|
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* Construit la section Telemetry
|
|
*/
|
|
_buildTelemetrySection() {
|
|
const group = new Adw.PreferencesGroup({
|
|
title: 'Telemetry Metrics',
|
|
description: 'Configure system monitoring metrics',
|
|
});
|
|
|
|
// Global telemetry switch
|
|
this._telemetryGlobalRow = new Adw.ActionRow({
|
|
title: 'Enable Telemetry',
|
|
subtitle: 'Master switch for all metrics',
|
|
});
|
|
|
|
const telemetrySwitch = new Gtk.Switch({
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
telemetrySwitch.connect('notify::active', (sw) => {
|
|
this._yamlConfig.setTelemetryEnabled(sw.active);
|
|
this._markDirty();
|
|
});
|
|
this._telemetrySwitch = telemetrySwitch;
|
|
|
|
this._telemetryGlobalRow.add_suffix(telemetrySwitch);
|
|
this._telemetryGlobalRow.activatable_widget = telemetrySwitch;
|
|
|
|
group.add(this._telemetryGlobalRow);
|
|
|
|
// Container pour les métriques individuelles
|
|
this._telemetryMetricsBox = new Gtk.Box({
|
|
orientation: Gtk.Orientation.VERTICAL,
|
|
spacing: 0,
|
|
});
|
|
|
|
group.add(this._telemetryMetricsBox);
|
|
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* Construit la section Commands
|
|
*/
|
|
_buildCommandsSection() {
|
|
const group = new Adw.PreferencesGroup({
|
|
title: 'Commands',
|
|
description: 'Configure allowed system commands',
|
|
});
|
|
|
|
// Global commands switch
|
|
this._commandsGlobalRow = new Adw.ActionRow({
|
|
title: 'Enable Commands',
|
|
subtitle: 'Master switch for all commands',
|
|
});
|
|
|
|
const commandsSwitch = new Gtk.Switch({
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
commandsSwitch.connect('notify::active', (sw) => {
|
|
this._yamlConfig.setCommandsEnabled(sw.active);
|
|
this._markDirty();
|
|
});
|
|
this._commandsSwitch = commandsSwitch;
|
|
|
|
this._commandsGlobalRow.add_suffix(commandsSwitch);
|
|
this._commandsGlobalRow.activatable_widget = commandsSwitch;
|
|
|
|
group.add(this._commandsGlobalRow);
|
|
|
|
// Allowlist editor row
|
|
this._commandsAllowlistRow = new Adw.ActionRow({
|
|
title: 'Allowed Commands',
|
|
subtitle: 'Click to edit the allowlist',
|
|
});
|
|
|
|
const editButton = new Gtk.Button({
|
|
icon_name: 'document-edit-symbolic',
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
editButton.connect('clicked', () => {
|
|
this._editCommandsAllowlist();
|
|
});
|
|
|
|
this._commandsAllowlistRow.add_suffix(editButton);
|
|
this._commandsAllowlistRow.set_activatable(true);
|
|
this._commandsAllowlistRow.connect('activated', () => {
|
|
this._editCommandsAllowlist();
|
|
});
|
|
|
|
group.add(this._commandsAllowlistRow);
|
|
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* Charge les données depuis la config YAML
|
|
*/
|
|
_loadData() {
|
|
const config = this._yamlConfig.load();
|
|
|
|
if (!config) {
|
|
this._showError('Failed to load configuration');
|
|
return;
|
|
}
|
|
|
|
// Update service status
|
|
this._updateServiceStatus();
|
|
|
|
// Update telemetry section
|
|
const telemetryEnabled = config.features?.telemetry?.enabled || false;
|
|
this._telemetrySwitch.active = telemetryEnabled;
|
|
|
|
// Clear existing metrics
|
|
let child = this._telemetryMetricsBox.get_first_child();
|
|
while (child) {
|
|
const next = child.get_next_sibling();
|
|
this._telemetryMetricsBox.remove(child);
|
|
child = next;
|
|
}
|
|
|
|
// Add metrics
|
|
const metrics = this._yamlConfig.getTelemetryMetrics();
|
|
for (const [name, metricConfig] of Object.entries(metrics)) {
|
|
this._addMetricRow(name, metricConfig);
|
|
}
|
|
|
|
// Update commands section
|
|
const commandsEnabled = config.features?.commands?.enabled || false;
|
|
this._commandsSwitch.active = commandsEnabled;
|
|
|
|
const allowlist = this._yamlConfig.getCommandsAllowlist();
|
|
this._commandsAllowlistRow.subtitle = `${allowlist.length} commands allowed`;
|
|
|
|
this._dirtyConfig = false;
|
|
}
|
|
|
|
/**
|
|
* Ajoute une ligne pour une métrique
|
|
*/
|
|
_addMetricRow(name, metricConfig) {
|
|
const row = new Adw.ActionRow({
|
|
title: metricConfig.name || name,
|
|
subtitle: `Interval: ${metricConfig.interval_s || 'N/A'}s`,
|
|
});
|
|
|
|
// Switch pour enable/disable
|
|
const metricSwitch = new Gtk.Switch({
|
|
active: metricConfig.enabled || false,
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
metricSwitch.connect('notify::active', (sw) => {
|
|
this._yamlConfig.updateTelemetryMetric(name, {enabled: sw.active});
|
|
this._markDirty();
|
|
});
|
|
|
|
// Bouton edit
|
|
const editButton = new Gtk.Button({
|
|
icon_name: 'document-edit-symbolic',
|
|
valign: Gtk.Align.CENTER,
|
|
});
|
|
editButton.connect('clicked', () => {
|
|
this._editMetric(name, metricConfig);
|
|
});
|
|
|
|
row.add_suffix(metricSwitch);
|
|
row.add_suffix(editButton);
|
|
|
|
this._telemetryMetricsBox.append(row);
|
|
}
|
|
|
|
/**
|
|
* Édite une métrique
|
|
*/
|
|
_editMetric(name, currentConfig) {
|
|
const dialog = new MetricEditDialog(this, name, currentConfig);
|
|
|
|
dialog.connect('response', (dlg, responseId) => {
|
|
if (responseId === Gtk.ResponseType.OK) {
|
|
const updates = dialog.getUpdates();
|
|
this._yamlConfig.updateTelemetryMetric(name, updates);
|
|
this._markDirty();
|
|
this._loadData();
|
|
}
|
|
dialog.destroy();
|
|
});
|
|
|
|
dialog.present();
|
|
}
|
|
|
|
/**
|
|
* Édite la allowlist des commandes
|
|
*/
|
|
_editCommandsAllowlist() {
|
|
const currentAllowlist = this._yamlConfig.getCommandsAllowlist();
|
|
const dialog = new CommandEditDialog(this, currentAllowlist);
|
|
|
|
dialog.connect('response', (dlg, responseId) => {
|
|
if (responseId === Gtk.ResponseType.OK) {
|
|
const newAllowlist = dialog.getAllowlist();
|
|
this._yamlConfig.updateCommandsAllowlist(newAllowlist);
|
|
this._markDirty();
|
|
this._loadData();
|
|
}
|
|
dialog.destroy();
|
|
});
|
|
|
|
dialog.present();
|
|
}
|
|
|
|
/**
|
|
* Met à jour le status du service
|
|
*/
|
|
_updateServiceStatus() {
|
|
const isActive = this._serviceManager.isServiceActive();
|
|
const isEnabled = this._serviceManager.isServiceEnabled();
|
|
|
|
this._serviceSwitch.active = isActive;
|
|
this._serviceEnableSwitch.active = isEnabled;
|
|
|
|
const statusText = isActive ? '🟢 Running' : '🔴 Stopped';
|
|
this._serviceStatusRow.subtitle = statusText;
|
|
}
|
|
|
|
/**
|
|
* Marque la config comme modifiée
|
|
*/
|
|
_markDirty() {
|
|
this._dirtyConfig = true;
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde la configuration
|
|
*/
|
|
_saveConfig() {
|
|
if (!this._dirtyConfig) {
|
|
this._showInfo('No changes to save');
|
|
return;
|
|
}
|
|
|
|
const success = this._yamlConfig.save();
|
|
|
|
if (success) {
|
|
this._dirtyConfig = false;
|
|
this._showInfo('Configuration saved successfully');
|
|
|
|
// Recharger le service
|
|
this._serviceManager.reloadService();
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
|
|
this._updateServiceStatus();
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
} else {
|
|
this._showError('Failed to save configuration');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'information
|
|
*/
|
|
_showInfo(message) {
|
|
const toast = new Adw.Toast({
|
|
title: message,
|
|
timeout: 2,
|
|
});
|
|
|
|
// Note: Toast overlay nécessite Adw.ToastOverlay
|
|
// Pour l'instant, on utilise console.log
|
|
console.log(`Info: ${message}`);
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'erreur
|
|
*/
|
|
_showError(message) {
|
|
const dialog = new Adw.MessageDialog({
|
|
transient_for: this,
|
|
heading: 'Error',
|
|
body: message,
|
|
});
|
|
|
|
dialog.add_response('ok', 'OK');
|
|
dialog.set_default_response('ok');
|
|
dialog.set_close_response('ok');
|
|
|
|
dialog.present();
|
|
}
|
|
});
|