From 2b684da9cdf7f03aad7756fd2754e78fee675366 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Sat, 6 Jun 2026 07:53:47 +0200 Subject: [PATCH] =?UTF-8?q?feat(api):=20profil=20machine=20=C3=A9ditable,?= =?UTF-8?q?=20sonde,=20et=20r=C3=A9glages=20globaux=20apt-cacher-ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - machines : updateMachine (PATCH /machines/:id) + POST /machines/:id/probe (sonde synchrone → faits + proposition de correction) ; MachineView expose machineKind/virtualization ; CreateMachineInput accepte aptProxyMode persistent - app_settings (clé/valeur, migration 0006) + service appSettings : défaut apt-cacher-ng (mode + url) ; applyProxyToAllMachines - routes /settings : GET, PUT /apt-proxy, POST /apt-proxy/apply-all Co-Authored-By: Claude Opus 4.8 --- server/db/migrations/0006_many_northstar.sql | 5 + server/db/migrations/meta/0006_snapshot.json | 2217 ++++++++++++++++++ server/db/migrations/meta/_journal.json | 7 + server/db/schema.ts | 7 + server/routes/index.ts | 2 + server/routes/machines.ts | 24 +- server/routes/settings.ts | 24 + server/services/appSettings.ts | 43 + server/services/machines.ts | 39 +- shared/types.ts | 3 + 10 files changed, 2366 insertions(+), 5 deletions(-) create mode 100644 server/db/migrations/0006_many_northstar.sql create mode 100644 server/db/migrations/meta/0006_snapshot.json create mode 100644 server/routes/settings.ts create mode 100644 server/services/appSettings.ts diff --git a/server/db/migrations/0006_many_northstar.sql b/server/db/migrations/0006_many_northstar.sql new file mode 100644 index 0000000..1830e12 --- /dev/null +++ b/server/db/migrations/0006_many_northstar.sql @@ -0,0 +1,5 @@ +CREATE TABLE `app_settings` ( + `key` text PRIMARY KEY NOT NULL, + `value` text, + `updated_at` text NOT NULL +); diff --git a/server/db/migrations/meta/0006_snapshot.json b/server/db/migrations/meta/0006_snapshot.json new file mode 100644 index 0000000..2231c2f --- /dev/null +++ b/server/db/migrations/meta/0006_snapshot.json @@ -0,0 +1,2217 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "b6542545-9bb8-448c-95f7-4feef066f128", + "prevId": "c9c200ce-086a-4c2c-8b95-1c295ab7ef2b", + "tables": { + "action_requests": { + "name": "action_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_by_type": { + "name": "requested_by_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by_id": { + "name": "requested_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "approved_at": { + "name": "approved_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "approved_by": { + "name": "approved_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "action_requests_machine_id_machines_id_fk": { + "name": "action_requests_machine_id_machines_id_fk", + "tableFrom": "action_requests", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_clients": { + "name": "api_clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes_json": { + "name": "scopes_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "api_clients_token_hash_unique": { + "name": "api_clients_token_hash_unique", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_settings": { + "name": "app_settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "docker_compose_roots": { + "name": "docker_compose_roots", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "scan_depth": { + "name": "scan_depth", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "docker_compose_roots_machine_id_machines_id_fk": { + "name": "docker_compose_roots_machine_id_machines_id_fk", + "tableFrom": "docker_compose_roots", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "docker_compose_stacks": { + "name": "docker_compose_stacks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "compose_files_json": { + "name": "compose_files_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_name": { + "name": "project_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "env_file": { + "name": "env_file", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detected_by": { + "name": "detected_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_scan_at": { + "name": "last_scan_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_update_at": { + "name": "last_update_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "docker_compose_stacks_machine_id_machines_id_fk": { + "name": "docker_compose_stacks_machine_id_machines_id_fk", + "tableFrom": "docker_compose_stacks", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "docker_image_events": { + "name": "docker_image_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stack_id": { + "name": "stack_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_image_id": { + "name": "from_image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "to_image_id": { + "name": "to_image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_digest": { + "name": "from_digest", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "to_digest": { + "name": "to_digest", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bytes_reclaimed": { + "name": "bytes_reclaimed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "docker_image_events_execution_id_executions_id_fk": { + "name": "docker_image_events_execution_id_executions_id_fk", + "tableFrom": "docker_image_events", + "tableTo": "executions", + "columnsFrom": [ + "execution_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "docker_settings": { + "name": "docker_settings", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "scan_depth": { + "name": "scan_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 4 + }, + "prune_mode": { + "name": "prune_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'safe'" + }, + "last_scan_at": { + "name": "last_scan_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_pull_check_at": { + "name": "last_pull_check_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "docker_settings_machine_id_machines_id_fk": { + "name": "docker_settings_machine_id_machines_id_fk", + "tableFrom": "docker_settings", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "docker_stack_services": { + "name": "docker_stack_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "stack_id": { + "name": "stack_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "current_image_id": { + "name": "current_image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "current_digest": { + "name": "current_digest", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "candidate_image_id": { + "name": "candidate_image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "candidate_digest": { + "name": "candidate_digest", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version_label": { + "name": "version_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "docker_stack_services_stack_id_docker_compose_stacks_id_fk": { + "name": "docker_stack_services_stack_id_docker_compose_stacks_id_fk", + "tableFrom": "docker_stack_services", + "tableTo": "docker_compose_stacks", + "columnsFrom": [ + "stack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "executions": { + "name": "executions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_json": { + "name": "result_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "important_json": { + "name": "important_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_path": { + "name": "report_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_log_path": { + "name": "raw_log_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_kind": { + "name": "error_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "executions_machine_id_machines_id_fk": { + "name": "executions_machine_id_machines_id_fk", + "tableFrom": "executions", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "important_messages": { + "name": "important_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_line_ref": { + "name": "raw_line_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acknowledged": { + "name": "acknowledged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "important_messages_machine_id_machines_id_fk": { + "name": "important_messages_machine_id_machines_id_fk", + "tableFrom": "important_messages", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_credentials": { + "name": "machine_credentials", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "auth_method": { + "name": "auth_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enc_password": { + "name": "enc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enc_sudo_password": { + "name": "enc_sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enc_private_key": { + "name": "enc_private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enc_key_passphrase": { + "name": "enc_key_passphrase", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_mode": { + "name": "sudo_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_test_at": { + "name": "last_test_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_credentials_machine_id_machines_id_fk": { + "name": "machine_credentials_machine_id_machines_id_fk", + "tableFrom": "machine_credentials", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_events": { + "name": "machine_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_events_machine_id_machines_id_fk": { + "name": "machine_events_machine_id_machines_id_fk", + "tableFrom": "machine_events", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_hardware": { + "name": "machine_hardware", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "probe_snapshot_id": { + "name": "probe_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cpu_model": { + "name": "cpu_model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cpu_cores": { + "name": "cpu_cores", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_bytes": { + "name": "memory_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gpus_json": { + "name": "gpus_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disks_json": { + "name": "disks_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "network_json": { + "name": "network_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "firmware_json": { + "name": "firmware_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "driver_json": { + "name": "driver_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "warnings_json": { + "name": "warnings_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_hardware_machine_id_machines_id_fk": { + "name": "machine_hardware_machine_id_machines_id_fk", + "tableFrom": "machine_hardware", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_host_keys": { + "name": "machine_host_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_host_keys_machine_id_machines_id_fk": { + "name": "machine_host_keys_machine_id_machines_id_fk", + "tableFrom": "machine_host_keys", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_metrics_latest": { + "name": "machine_metrics_latest", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "collected_at": { + "name": "collected_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cpu_load1": { + "name": "cpu_load1", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cpu_load5": { + "name": "cpu_load5", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cpu_cores": { + "name": "cpu_cores", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_total_bytes": { + "name": "memory_total_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_used_bytes": { + "name": "memory_used_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_available_bytes": { + "name": "memory_available_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_used_percent": { + "name": "memory_used_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "filesystems_json": { + "name": "filesystems_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_used_percent": { + "name": "root_used_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "warnings_json": { + "name": "warnings_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_metrics_latest_machine_id_machines_id_fk": { + "name": "machine_metrics_latest_machine_id_machines_id_fk", + "tableFrom": "machine_metrics_latest", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machine_state": { + "name": "machine_state", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "apt_status": { + "name": "apt_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "apt_updates_count": { + "name": "apt_updates_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "apt_reboot_required": { + "name": "apt_reboot_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "apt_last_analyze_at": { + "name": "apt_last_analyze_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "docker_status": { + "name": "docker_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "docker_installed": { + "name": "docker_installed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "docker_stacks_count": { + "name": "docker_stacks_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "docker_updates_count": { + "name": "docker_updates_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "docker_prune_available": { + "name": "docker_prune_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "post_install_status": { + "name": "post_install_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metrics_last_collected_at": { + "name": "metrics_last_collected_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cpu_load1": { + "name": "cpu_load1", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memory_used_percent": { + "name": "memory_used_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_used_percent": { + "name": "root_used_percent", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disk_warnings_count": { + "name": "disk_warnings_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "hardware_warnings_count": { + "name": "hardware_warnings_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "running_job_id": { + "name": "running_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error_kind": { + "name": "last_error_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "machine_state_machine_id_machines_id_fk": { + "name": "machine_state_machine_id_machines_id_fk", + "tableFrom": "machine_state", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "machines": { + "name": "machines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 22 + }, + "os_family": { + "name": "os_family", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "os_version": { + "name": "os_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "os_codename": { + "name": "os_codename", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "machine_kind": { + "name": "machine_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "virtualization": { + "name": "virtualization", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hardware_profile": { + "name": "hardware_profile", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enc_password": { + "name": "enc_password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enc_sudo_password": { + "name": "enc_sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "apt_proxy_mode": { + "name": "apt_proxy_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'direct'" + }, + "apt_proxy_url": { + "name": "apt_proxy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "raw_artifacts": { + "name": "raw_artifacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "redacted": { + "name": "redacted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_reason": { + "name": "delete_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "raw_artifacts_machine_id_machines_id_fk": { + "name": "raw_artifacts_machine_id_machines_id_fk", + "tableFrom": "raw_artifacts", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reports": { + "name": "reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "summary_json": { + "name": "summary_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "reports_machine_id_machines_id_fk": { + "name": "reports_machine_id_machines_id_fk", + "tableFrom": "reports", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "snapshots": { + "name": "snapshots", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'apt_update_analyze'" + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "checked_at": { + "name": "checked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "important_json": { + "name": "important_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_log_path": { + "name": "raw_log_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_artifact_id": { + "name": "raw_artifact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_job_id": { + "name": "source_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "snapshots_machine_id_machines_id_fk": { + "name": "snapshots_machine_id_machines_id_fk", + "tableFrom": "snapshots", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/server/db/migrations/meta/_journal.json b/server/db/migrations/meta/_journal.json index 95b9306..4f5be15 100644 --- a/server/db/migrations/meta/_journal.json +++ b/server/db/migrations/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1780718324238, "tag": "0005_silent_drax", "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1780724800966, + "tag": "0006_many_northstar", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/db/schema.ts b/server/db/schema.ts index 30adf34..3d3fb92 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -312,3 +312,10 @@ export const actionRequests = sqliteTable("action_requests", { executionId: text("execution_id"), expiresAt: text("expires_at"), }); + +// Réglages globaux de l'application (clé/valeur). Ex. proxy APT par défaut. +export const appSettings = sqliteTable("app_settings", { + key: text("key").primaryKey(), + value: text("value"), + updatedAt: text("updated_at").notNull(), +}); diff --git a/server/routes/index.ts b/server/routes/index.ts index 2840714..20c0be3 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -5,6 +5,7 @@ import { actionsRoutes } from "./actions.js"; import { actionRequestsRoutes } from "./actionRequests.js"; import { dockerRoutes } from "./docker.js"; import { dbRoutes } from "./db.js"; +import { settingsRoutes } from "./settings.js"; import { getServerCapabilities } from "../services/capabilities.js"; import { getSystemMetrics, getSystemStatus } from "../services/system.js"; @@ -13,6 +14,7 @@ api.get("/capabilities", (c) => c.json(getServerCapabilities())); api.get("/system/status", (c) => c.json(getSystemStatus())); api.get("/system/metrics", (c) => c.json(getSystemMetrics())); api.route("/system/db", dbRoutes); +api.route("/settings", settingsRoutes); api.route("/machines", machinesRoutes); api.route("/machines", actionsRoutes); api.route("/machines", dockerRoutes); diff --git a/server/routes/machines.ts b/server/routes/machines.ts index 169d71d..3059232 100644 --- a/server/routes/machines.ts +++ b/server/routes/machines.ts @@ -1,10 +1,11 @@ // server/routes/machines.ts import { Hono } from "hono"; import { - listMachines, createMachine, deleteMachine, getMachineRow, getCreds, testConnection, - type CreateMachineInput, + listMachines, createMachine, deleteMachine, updateMachine, getMachineRow, getCreds, testConnection, + type CreateMachineInput, type UpdateMachineInput, } from "../services/machines.js"; import { refreshMachine, getLatestSnapshot } from "../services/refresh.js"; +import { runProbe } from "../services/machineProbe.js"; export const machinesRoutes = new Hono(); @@ -43,6 +44,25 @@ machinesRoutes.post("/:id/refresh", async (c) => { } }); +machinesRoutes.patch("/:id", async (c) => { + const body = (await c.req.json()) as UpdateMachineInput; + try { + return c.json(updateMachine(c.req.param("id"), body)); + } catch (err) { + return c.json({ error: (err as Error).message }, 400); + } +}); + +// Sonde synchrone (lecture seule) : renvoie faits + proposition de correction. +machinesRoutes.post("/:id/probe", async (c) => { + try { + const o = await runProbe(c.req.param("id")); + return c.json({ probe: o.probe, proposal: o.proposal, changes: o.changes }); + } catch (err) { + return c.json({ error: (err as Error).message }, 400); + } +}); + machinesRoutes.delete("/:id", (c) => { deleteMachine(c.req.param("id")); return c.json({ ok: true }); diff --git a/server/routes/settings.ts b/server/routes/settings.ts new file mode 100644 index 0000000..107744f --- /dev/null +++ b/server/routes/settings.ts @@ -0,0 +1,24 @@ +// server/routes/settings.ts +import { Hono } from "hono"; +import { getDefaultAptProxy, setDefaultAptProxy, type DefaultAptProxy } from "../services/appSettings.js"; +import { applyProxyToAllMachines } from "../services/machines.js"; + +export const settingsRoutes = new Hono(); + +// Réglages globaux exposés à l'UI. +settingsRoutes.get("/", (c) => c.json({ defaultAptProxy: getDefaultAptProxy() })); + +// Définit le proxy APT par défaut (apt-cacher-ng). +settingsRoutes.put("/apt-proxy", async (c) => { + const body = (await c.req.json()) as DefaultAptProxy; + const mode = body.mode ?? "direct"; + const url = (body.url ?? "").trim() || null; + return c.json(setDefaultAptProxy({ mode, url })); +}); + +// Applique le proxy par défaut à toutes les machines existantes. +settingsRoutes.post("/apt-proxy/apply-all", (c) => { + const { mode, url } = getDefaultAptProxy(); + const updated = applyProxyToAllMachines(mode, url); + return c.json({ ok: true, updated }); +}); diff --git a/server/services/appSettings.ts b/server/services/appSettings.ts new file mode 100644 index 0000000..9a65f5b --- /dev/null +++ b/server/services/appSettings.ts @@ -0,0 +1,43 @@ +// server/services/appSettings.ts +import { db, schema } from "../db/client.js"; +import type { AptProxyMode } from "@shared/types.js"; + +export const SETTING_KEYS = { + defaultAptProxyUrl: "default_apt_proxy_url", + defaultAptProxyMode: "default_apt_proxy_mode", +} as const; + +export function getAllSettings(): Record { + return Object.fromEntries( + db.select().from(schema.appSettings).all().map((r) => [r.key, r.value ?? ""]), + ); +} + +export function setSettings(patch: Record): void { + const now = new Date().toISOString(); + for (const [key, value] of Object.entries(patch)) { + db.insert(schema.appSettings) + .values({ key, value, updatedAt: now }) + .onConflictDoUpdate({ target: schema.appSettings.key, set: { value, updatedAt: now } }) + .run(); + } +} + +export interface DefaultAptProxy { + mode: AptProxyMode; + url: string | null; +} + +export function getDefaultAptProxy(): DefaultAptProxy { + const s = getAllSettings(); + const mode = (s[SETTING_KEYS.defaultAptProxyMode] as AptProxyMode) || "direct"; + return { mode, url: s[SETTING_KEYS.defaultAptProxyUrl] || null }; +} + +export function setDefaultAptProxy(input: DefaultAptProxy): DefaultAptProxy { + setSettings({ + [SETTING_KEYS.defaultAptProxyMode]: input.mode, + [SETTING_KEYS.defaultAptProxyUrl]: input.url ?? "", + }); + return getDefaultAptProxy(); +} diff --git a/server/services/machines.ts b/server/services/machines.ts index ee1c618..c7bb693 100644 --- a/server/services/machines.ts +++ b/server/services/machines.ts @@ -5,7 +5,7 @@ import { db, schema } from "../db/client.js"; import { encryptSecret, decryptSecret } from "../crypto/secrets.js"; import { env } from "../env.js"; import { runPlain, type SshCreds } from "../ssh/client.js"; -import type { MachineView, OsFamily } from "@shared/types.js"; +import type { AptProxyMode, MachineKind, MachineView, OsFamily } from "@shared/types.js"; import { writeCredentials, readCredentials, resolveCreds } from "./credentials.js"; export interface CreateMachineInput { @@ -15,7 +15,7 @@ export interface CreateMachineInput { username: string; password: string; sudoPassword?: string | null; - aptProxyMode?: "direct" | "runtime"; + aptProxyMode?: AptProxyMode; aptProxyUrl?: string | null; } @@ -29,13 +29,37 @@ function toView(m: MachineRow): MachineView { port: m.port, osFamily: m.osFamily as OsFamily, username: m.username, - aptProxyMode: m.aptProxyMode as "direct" | "runtime", + aptProxyMode: m.aptProxyMode as AptProxyMode, aptProxyUrl: m.aptProxyUrl, status: m.status as MachineView["status"], lastCheckedAt: m.lastCheckedAt, + machineKind: (m.machineKind as MachineKind | null) ?? "unknown", + virtualization: m.virtualization, }; } +export interface UpdateMachineInput { + osFamily?: OsFamily; + machineKind?: MachineKind; + virtualization?: string | null; + aptProxyMode?: AptProxyMode; + aptProxyUrl?: string | null; +} + +/** Met à jour les champs de profil/proxy d'une machine (jamais les secrets). */ +export function updateMachine(id: string, input: UpdateMachineInput): MachineView { + const row = getMachineRow(id); + if (!row) throw new Error("Machine introuvable"); + const patch: Partial = { updatedAt: new Date().toISOString() }; + if (input.osFamily !== undefined) patch.osFamily = input.osFamily; + if (input.machineKind !== undefined) patch.machineKind = input.machineKind; + if (input.virtualization !== undefined) patch.virtualization = input.virtualization; + if (input.aptProxyMode !== undefined) patch.aptProxyMode = input.aptProxyMode; + if (input.aptProxyUrl !== undefined) patch.aptProxyUrl = input.aptProxyUrl; + db.update(schema.machines).set(patch).where(eq(schema.machines.id, id)).run(); + return toView(getMachineRow(id)!); +} + export function getCreds(m: MachineRow): SshCreds { const key = env.requireMasterKey(); const { encPassword, encSudoPassword } = resolveCreds( @@ -121,3 +145,12 @@ export async function createMachine(input: CreateMachineInput): Promise