From c6cba96e339565389b8ac09dd8b995dc42316fa2 Mon Sep 17 00:00:00 2001 From: Ciro Iriarte Date: Sat, 11 Jul 2026 08:57:40 -0300 Subject: [PATCH 1/5] GUACAMOLE-2300: Add IPMI protocol connection form and translations Makes IPMI a first-class protocol in the connection editor (previously only usable via user-mapping.xml). Adds the ipmi.json protocol form with grouped, progressive-disclosure sections (Network, Authentication, Security & Encryption, Serial-over-LAN, Power & Boot, Display, Clipboard, Terminal behavior, Typescript, Screen Recording), including ENUMs for privilege-level, cipher-suite (3/17), encryption-policy, BMC vendor workaround presets, power-on-connect, and boot-device; plus the PROTOCOL_IPMI English translations for all sections, fields, and options. --- .../org/apache/guacamole/protocols/ipmi.json | 246 ++++++++++++++++++ .../main/frontend/src/translations/en.json | 110 ++++++++ 2 files changed, 356 insertions(+) create mode 100644 guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ipmi.json diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ipmi.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ipmi.json new file mode 100644 index 0000000000..02b1bb7dd8 --- /dev/null +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ipmi.json @@ -0,0 +1,246 @@ +{ + "name" : "ipmi", + "connectionForms" : [ + + { + "name" : "network", + "fields" : [ + { + "name" : "hostname", + "type" : "TEXT" + }, + { + "name" : "port", + "type" : "NUMERIC" + }, + { + "name" : "timeout", + "type" : "NUMERIC" + } + ] + }, + + { + "name" : "authentication", + "fields" : [ + { + "name" : "username", + "type" : "USERNAME" + }, + { + "name" : "password", + "type" : "PASSWORD" + }, + { + "name" : "k-g", + "type" : "PASSWORD" + }, + { + "name" : "privilege-level", + "type" : "ENUM", + "options" : [ "", "user", "operator", "admin" ] + } + ] + }, + + { + "name" : "security", + "fields" : [ + { + "name" : "cipher-suite", + "type" : "ENUM", + "options" : [ "", "3", "17" ] + }, + { + "name" : "encryption-policy", + "type" : "ENUM", + "options" : [ "", "required", "preferred", "none" ] + }, + { + "name" : "workaround-flags", + "type" : "ENUM", + "options" : [ "", "supermicro", "intel", "sun", "dell", "hpe", "lenovo" ] + } + ] + }, + + { + "name" : "serial-over-lan", + "fields" : [ + { + "name" : "sol-payload-instance", + "type" : "NUMERIC" + }, + { + "name" : "keepalive-interval", + "type" : "NUMERIC" + } + ] + }, + + { + "name" : "power", + "fields" : [ + { + "name" : "power-on-connect", + "type" : "ENUM", + "options" : [ "", "none", "on", "reset", "cycle" ] + }, + { + "name" : "boot-device", + "type" : "ENUM", + "options" : [ "", "none", "pxe", "disk", "cdrom", "bios" ] + }, + { + "name" : "boot-device-persistent", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + }, + + { + "name" : "display", + "fields" : [ + { + "name" : "color-scheme", + "type" : "TERMINAL_COLOR_SCHEME", + "options" : [ "", "black-white", "gray-black", "green-black", "white-black" ] + }, + { + "name" : "font-name", + "type" : "TEXT" + }, + { + "name" : "font-size", + "type" : "ENUM", + "options" : [ "", "8", "9", "10", "11", "12", "14", "18", "24", "30", "36", "48", "60", "72", "96" ] + }, + { + "name" : "scrollback", + "type" : "NUMERIC" + }, + { + "name" : "read-only", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + }, + + { + "name" : "clipboard", + "fields" : [ + { + "name" : "disable-copy", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "disable-paste", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + }, + + { + "name" : "behavior", + "fields" : [ + { + "name" : "backspace", + "type" : "ENUM", + "options" : [ "", "127", "8" ] + }, + { + "name" : "terminal-type", + "type" : "ENUM", + "options" : [ "", "xterm", "xterm-256color", "vt220", "vt100", "ansi", "linux" ] + } + ] + }, + + { + "name" : "typescript", + "fields" : [ + { + "name" : "typescript-path", + "type" : "TEXT" + }, + { + "name" : "typescript-name", + "type" : "TEXT" + }, + { + "name" : "create-typescript-path", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "typescript-write-existing", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + }, + + { + "name" : "recording", + "fields" : [ + { + "name" : "recording-path", + "type" : "TEXT" + }, + { + "name" : "recording-name", + "type" : "TEXT" + }, + { + "name" : "recording-exclude-output", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "recording-exclude-mouse", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "recording-include-keys", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "create-recording-path", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, + { + "name" : "recording-write-existing", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + } + + ], + "sharingProfileForms" : [ + + { + "name" : "display", + "fields" : [ + { + "name" : "read-only", + "type" : "BOOLEAN", + "options" : [ "true" ] + } + ] + } + + ] +} diff --git a/guacamole/src/main/frontend/src/translations/en.json b/guacamole/src/main/frontend/src/translations/en.json index 27f24a9762..d3339eefc5 100644 --- a/guacamole/src/main/frontend/src/translations/en.json +++ b/guacamole/src/main/frontend/src/translations/en.json @@ -505,6 +505,116 @@ }, + "PROTOCOL_IPMI" : { + "FIELD_HEADER_BACKSPACE": "Backspace key sends:", + "FIELD_HEADER_BOOT_DEVICE": "Boot device override:", + "FIELD_HEADER_BOOT_DEVICE_PERSISTENT": "Make boot override persistent:", + "FIELD_HEADER_CIPHER_SUITE": "Cipher suite:", + "FIELD_HEADER_COLOR_SCHEME": "Color scheme:", + "FIELD_HEADER_CREATE_RECORDING_PATH": "Automatically create recording path:", + "FIELD_HEADER_CREATE_TYPESCRIPT_PATH": "Automatically create typescript path:", + "FIELD_HEADER_DISABLE_COPY": "Disable copying from terminal:", + "FIELD_HEADER_DISABLE_PASTE": "Disable pasting from client:", + "FIELD_HEADER_ENCRYPTION_POLICY": "Encryption policy:", + "FIELD_HEADER_FONT_NAME": "Font name:", + "FIELD_HEADER_FONT_SIZE": "Font size:", + "FIELD_HEADER_HOSTNAME": "Hostname:", + "FIELD_HEADER_KEEPALIVE_INTERVAL": "SOL keepalive interval (seconds):", + "FIELD_HEADER_K_G": "BMC key (K_g):", + "FIELD_HEADER_PASSWORD": "Password:", + "FIELD_HEADER_PASSWORD_REGEX": "Password regular expression:", + "FIELD_HEADER_PORT": "Port:", + "FIELD_HEADER_POWER_ON_CONNECT": "Power action on connect:", + "FIELD_HEADER_PRIVILEGE_LEVEL": "Privilege level:", + "FIELD_HEADER_READ_ONLY": "Read-only:", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE": "Exclude mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT": "Exclude graphics/streams:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD": "Include clipboard events:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS": "Include key events:", + "FIELD_HEADER_RECORDING_NAME": "Recording name:", + "FIELD_HEADER_RECORDING_PATH": "Recording path:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING": "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_SCROLLBACK": "Maximum scrollback size:", + "FIELD_HEADER_SOL_PAYLOAD_INSTANCE": "SOL payload instance:", + "FIELD_HEADER_TERMINAL_TYPE": "Terminal type:", + "FIELD_HEADER_TIMEOUT": "Session timeout (seconds):", + "FIELD_HEADER_TYPESCRIPT_NAME": "Typescript name:", + "FIELD_HEADER_TYPESCRIPT_PATH": "Typescript path:", + "FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING": "@:APP.FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING", + "FIELD_HEADER_USERNAME": "Username:", + "FIELD_HEADER_USERNAME_REGEX": "Username regular expression:", + "FIELD_HEADER_WORKAROUND_FLAGS": "BMC vendor workaround preset:", + "FIELD_OPTION_BACKSPACE_127": "Delete (Ctrl-?)", + "FIELD_OPTION_BACKSPACE_8": "Backspace (Ctrl-H)", + "FIELD_OPTION_BACKSPACE_EMPTY": "", + "FIELD_OPTION_BOOT_DEVICE_BIOS": "BIOS/UEFI setup", + "FIELD_OPTION_BOOT_DEVICE_CDROM": "CD/DVD", + "FIELD_OPTION_BOOT_DEVICE_DISK": "Hard disk", + "FIELD_OPTION_BOOT_DEVICE_EMPTY": "", + "FIELD_OPTION_BOOT_DEVICE_NONE": "No override", + "FIELD_OPTION_BOOT_DEVICE_PXE": "Network (PXE)", + "FIELD_OPTION_CIPHER_SUITE_17": "17 (HMAC-SHA256 / AES-CBC-128)", + "FIELD_OPTION_CIPHER_SUITE_3": "3 (HMAC-SHA1 / AES-CBC-128)", + "FIELD_OPTION_CIPHER_SUITE_EMPTY": "", + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE": "Black on white", + "FIELD_OPTION_COLOR_SCHEME_EMPTY": "", + "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK": "Gray on black", + "FIELD_OPTION_COLOR_SCHEME_GREEN_BLACK": "Green on black", + "FIELD_OPTION_COLOR_SCHEME_WHITE_BLACK": "White on black", + "FIELD_OPTION_ENCRYPTION_POLICY_EMPTY": "", + "FIELD_OPTION_ENCRYPTION_POLICY_NONE": "None (insecure)", + "FIELD_OPTION_ENCRYPTION_POLICY_PREFERRED": "Preferred", + "FIELD_OPTION_ENCRYPTION_POLICY_REQUIRED": "Required", + "FIELD_OPTION_FONT_SIZE_10": "10", + "FIELD_OPTION_FONT_SIZE_11": "11", + "FIELD_OPTION_FONT_SIZE_12": "12", + "FIELD_OPTION_FONT_SIZE_14": "14", + "FIELD_OPTION_FONT_SIZE_18": "18", + "FIELD_OPTION_FONT_SIZE_24": "24", + "FIELD_OPTION_FONT_SIZE_30": "30", + "FIELD_OPTION_FONT_SIZE_36": "36", + "FIELD_OPTION_FONT_SIZE_48": "48", + "FIELD_OPTION_FONT_SIZE_60": "60", + "FIELD_OPTION_FONT_SIZE_72": "72", + "FIELD_OPTION_FONT_SIZE_8": "8", + "FIELD_OPTION_FONT_SIZE_9": "9", + "FIELD_OPTION_FONT_SIZE_96": "96", + "FIELD_OPTION_FONT_SIZE_EMPTY": "", + "FIELD_OPTION_POWER_ON_CONNECT_CYCLE": "Power cycle", + "FIELD_OPTION_POWER_ON_CONNECT_EMPTY": "", + "FIELD_OPTION_POWER_ON_CONNECT_NONE": "None", + "FIELD_OPTION_POWER_ON_CONNECT_ON": "Power on", + "FIELD_OPTION_POWER_ON_CONNECT_RESET": "Hard reset", + "FIELD_OPTION_PRIVILEGE_LEVEL_ADMIN": "Administrator", + "FIELD_OPTION_PRIVILEGE_LEVEL_EMPTY": "", + "FIELD_OPTION_PRIVILEGE_LEVEL_OPERATOR": "Operator", + "FIELD_OPTION_PRIVILEGE_LEVEL_USER": "User", + "FIELD_OPTION_TERMINAL_TYPE_ANSI": "ansi", + "FIELD_OPTION_TERMINAL_TYPE_EMPTY": "", + "FIELD_OPTION_TERMINAL_TYPE_LINUX": "linux", + "FIELD_OPTION_TERMINAL_TYPE_VT100": "vt100", + "FIELD_OPTION_TERMINAL_TYPE_VT220": "vt220", + "FIELD_OPTION_TERMINAL_TYPE_XTERM": "xterm", + "FIELD_OPTION_TERMINAL_TYPE_XTERM_256COLOR": "xterm-256color", + "FIELD_OPTION_WORKAROUND_FLAGS_DELL": "Dell iDRAC", + "FIELD_OPTION_WORKAROUND_FLAGS_EMPTY": "(none)", + "FIELD_OPTION_WORKAROUND_FLAGS_HPE": "HPE iLO", + "FIELD_OPTION_WORKAROUND_FLAGS_INTEL": "Intel", + "FIELD_OPTION_WORKAROUND_FLAGS_LENOVO": "Lenovo XCC", + "FIELD_OPTION_WORKAROUND_FLAGS_SUN": "Sun / Oracle", + "FIELD_OPTION_WORKAROUND_FLAGS_SUPERMICRO": "Supermicro", + "NAME": "IPMI (Serial over LAN)", + "SECTION_HEADER_AUTHENTICATION": "Authentication", + "SECTION_HEADER_BEHAVIOR": "Terminal behavior", + "SECTION_HEADER_CLIPBOARD": "Clipboard", + "SECTION_HEADER_DISPLAY": "Display", + "SECTION_HEADER_NETWORK": "Network", + "SECTION_HEADER_POWER": "Power & Boot", + "SECTION_HEADER_RECORDING": "Screen Recording", + "SECTION_HEADER_SECURITY": "Security & Encryption", + "SECTION_HEADER_SERIAL_OVER_LAN": "Serial-over-LAN", + "SECTION_HEADER_TYPESCRIPT": "Typescript (Text Session Recording)" + }, "PROTOCOL_KUBERNETES" : { "FIELD_HEADER_BACKSPACE" : "Backspace key sends:", From 16271ace9fd84ad468d919c0000811b341c189b3 Mon Sep 17 00:00:00 2001 From: Ciro Iriarte Date: Sat, 11 Jul 2026 11:10:19 -0300 Subject: [PATCH 2/5] GUACAMOLE-2300: Add IPMI chassis control panel to client menu Adds a guacIpmiControl directive that surfaces out-of-band chassis management for IPMI connections directly in the client menu, gated on protocol === 'ipmi'. Console I/O continues over the terminal; this panel communicates solely over the dedicated 'ipmi-control' pipe stream, exchanging newline-delimited JSON with the guacd IPMI module. Features: - Live power/SOL-health status badges with authoritative refresh - Power controls (on, soft shutdown, cycle, hard reset, off, NMI) with confirmation gating on disruptive actions - Chassis identify, serial break, and System Event Log viewer Includes directive, template, styles, and en translations. --- .../app/client/directives/guacIpmiControl.js | 319 ++++++++++++++++++ .../src/app/client/styles/ipmi-control.css | 100 ++++++ .../src/app/client/templates/client.html | 8 + .../app/client/templates/guacIpmiControl.html | 52 +++ .../main/frontend/src/translations/en.json | 23 +- 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 guacamole/src/main/frontend/src/app/client/directives/guacIpmiControl.js create mode 100644 guacamole/src/main/frontend/src/app/client/styles/ipmi-control.css create mode 100644 guacamole/src/main/frontend/src/app/client/templates/guacIpmiControl.html diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacIpmiControl.js b/guacamole/src/main/frontend/src/app/client/directives/guacIpmiControl.js new file mode 100644 index 0000000000..1c072d8c94 --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/directives/guacIpmiControl.js @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * A directive which provides out-of-band chassis management (power control, + * chassis identify, System Event Log, serial break) for connections using the + * IPMI protocol. Console I/O flows over the normal terminal; this directive + * communicates exclusively over the dedicated "ipmi-control" pipe stream, + * exchanging newline-delimited JSON with the guacd IPMI module. + */ +angular.module('client').directive('guacIpmiControl', [function guacIpmiControl() { + + const directive = { + restrict: 'E', + replace: true, + templateUrl: 'app/client/templates/guacIpmiControl.html' + }; + + directive.scope = { + + /** + * The ManagedClient of the IPMI connection to control. + * + * @type ManagedClient + */ + client : '=' + + }; + + directive.controller = ['$scope', '$injector', '$element', + function guacIpmiControlController($scope, $injector, $element) { + + // Required services + const ManagedClient = $injector.get('ManagedClient'); + + /** + * The name of the bidirectional control pipe stream, matching + * GUAC_IPMI_CONTROL_PIPE_NAME on the server. + * + * @constant + * @type String + */ + const PIPE_NAME = 'ipmi-control'; + + /** + * The set of power actions which are potentially disruptive and thus + * require explicit confirmation before being sent to the BMC. + * + * @type Object. + */ + const DESTRUCTIVE = { + 'power-off' : true, + 'power-cycle' : true, + 'hard-reset' : true, + 'diagnostic-interrupt' : true + }; + + /** + * The most recently reported chassis power state ("on", "off", or + * "unknown"). + * + * @type String + */ + $scope.power = 'unknown'; + + /** + * The most recently reported SOL session health ("sol-connected" or + * "sol-disconnected"). + * + * @type String + */ + $scope.health = 'sol-disconnected'; + + /** + * The rendered text of the System Event Log, if it has been read, or + * null if it has not yet been requested. + * + * @type String + */ + $scope.sel = null; + + /** + * The human-readable result of the most recent command, or null if no + * command has completed yet. + * + * @type String + */ + $scope.message = null; + + /** + * Whether the most recent command result indicated failure. + * + * @type Boolean + */ + $scope.messageError = false; + + /** + * The command awaiting confirmation, or null if no destructive command + * is currently pending confirmation. + * + * @type String + */ + $scope.pending = null; + + /** + * The translation-key suffix (uppercase, underscore-separated) of the + * pending destructive action, or null if none is pending. + * + * @type String + */ + $scope.pendingKey = null; + + /** + * Whether a command has been sent and no corresponding result has yet + * been received. + * + * @type Boolean + */ + $scope.busy = false; + + /** + * The outbound pipe stream / string writer used to send commands to + * the server, lazily created on first use. + * + * @type Guacamole.StringWriter + */ + let writer = null; + + /** + * A monotonically increasing counter used to generate unique command + * identifiers so results can be correlated with their requests. + * + * @type Number + */ + let nextId = 0; + + /** + * Handles a single complete inbound control message from the server. + * + * @param {Object} msg + * The parsed JSON message. + */ + const handleMessage = function handleMessage(msg) { + $scope.$evalAsync(function applyMessage() { + + switch (msg.type) { + + case 'state': + if (msg.power) $scope.power = msg.power; + if (msg.health) $scope.health = msg.health; + break; + + case 'result': + $scope.busy = false; + $scope.message = msg.message; + $scope.messageError = (msg.ok === false); + break; + + case 'sel': + $scope.sel = msg.error + ? ('[' + msg.error + ']') + : (msg.text || ''); + break; + + } + + }); + }; + + /** + * Deferred pipe stream handler for the "ipmi-control" stream. Each + * server message arrives as its own pipe stream carrying a single JSON + * object; this reassembles and parses that object. + */ + const pipeHandler = function pipeHandler(stream, mimetype) { + + const reader = new Guacamole.StringReader(stream); + let received = ''; + + reader.ontext = function ontext(text) { + received += text; + }; + + reader.onend = function onend() { + try { + handleMessage(JSON.parse(received)); + } + catch (ignore) { + // Ignore malformed messages + } + }; + + }; + + /** + * Sends the given command over the control pipe, generating a unique + * correlation id. + * + * @param {String} command + * The command to send (e.g. "power-on", "identify", "read-sel"). + */ + const send = function send(command) { + + const guacClient = $scope.client && $scope.client.client; + if (!guacClient) + return; + + // Lazily open the outbound control pipe + if (!writer) { + const stream = guacClient.createPipeStream('application/json', PIPE_NAME); + writer = new Guacamole.StringWriter(stream); + } + + const id = 'c' + (nextId++); + writer.sendText(JSON.stringify({ + type : 'command', + command : command, + id : id + }) + '\n'); + + $scope.busy = true; + $scope.message = null; + + }; + + /** + * Requests a fresh, authoritative power/health state from the BMC. + */ + $scope.refresh = function refresh() { + send('refresh-status'); + }; + + /** + * Requests the current System Event Log. + */ + $scope.readSel = function readSel() { + $scope.sel = ''; + send('read-sel'); + }; + + /** + * Activates the chassis identify LED. + */ + $scope.identify = function identify() { + send('identify'); + }; + + /** + * Sends a serial break over the active SOL session. + */ + $scope.sendBreak = function sendBreak() { + send('send-break'); + }; + + /** + * Invokes the given power action, first prompting for confirmation if + * the action is potentially disruptive. + * + * @param {String} action + * The power action to invoke (e.g. "power-on", "hard-reset"). + */ + $scope.power_action = function power_action(action) { + if (DESTRUCTIVE[action]) { + $scope.pending = action; + $scope.pendingKey = action.toUpperCase().replace(/-/g, '_'); + } + else + send(action); + }; + + /** + * Confirms and sends the currently pending destructive action. + */ + $scope.confirm = function confirm() { + const action = $scope.pending; + $scope.pending = null; + $scope.pendingKey = null; + if (action) + send(action); + }; + + /** + * Cancels the currently pending destructive action. + */ + $scope.cancel = function cancel() { + $scope.pending = null; + $scope.pendingKey = null; + }; + + // Register the control pipe handler as soon as a client is available, + // then request an authoritative initial state + $scope.$watch('client', function clientChanged(client) { + if (client && client.client) { + ManagedClient.registerDeferredPipeHandler(client, PIPE_NAME, pipeHandler); + $scope.refresh(); + } + }); + + }]; + + return directive; + +}]); diff --git a/guacamole/src/main/frontend/src/app/client/styles/ipmi-control.css b/guacamole/src/main/frontend/src/app/client/styles/ipmi-control.css new file mode 100644 index 0000000000..c50acbc2cf --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/styles/ipmi-control.css @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.ipmi-control .ipmi-status { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5em; + margin-bottom: 0.5em; +} + +.ipmi-control .ipmi-power-badge, +.ipmi-control .ipmi-health-badge { + display: inline-block; + padding: 0.15em 0.6em; + border-radius: 0.75em; + font-size: 0.85em; + font-weight: bold; + text-transform: uppercase; + color: white; + background: #888; +} + +.ipmi-control .ipmi-power-on { background: #2e7d32; } +.ipmi-control .ipmi-power-off { background: #b71c1c; } +.ipmi-control .ipmi-power-unknown { background: #888; } +.ipmi-control .ipmi-health-connected { background: #1565c0; } +.ipmi-control .ipmi-health-disconnected { background: #888; } + +.ipmi-control .ipmi-refresh { + margin-left: auto; +} + +.ipmi-control .ipmi-actions { + display: flex; + flex-wrap: wrap; + gap: 0.35em; + margin: 0.5em 0; +} + +.ipmi-control .ipmi-actions button { + flex: 1 1 45%; +} + +.ipmi-control button.ipmi-danger { + border-color: #b71c1c; + color: #b71c1c; +} + +.ipmi-control .ipmi-message { + margin: 0.5em 0; + font-size: 0.9em; +} + +.ipmi-control .ipmi-message-error { + color: #b71c1c; + font-weight: bold; +} + +.ipmi-control .ipmi-confirm { + border: 1px solid #b71c1c; + border-radius: 0.25em; + padding: 0.5em; + margin: 0.5em 0; +} + +.ipmi-control .ipmi-confirm-action { + font-weight: bold; + margin: 0.25em 0 0.5em 0; +} + +.ipmi-control .ipmi-sel { + margin-top: 0.5em; +} + +.ipmi-control .ipmi-sel pre { + max-height: 12em; + overflow: auto; + background: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.2); + padding: 0.5em; + font-size: 0.75em; + white-space: pre; +} diff --git a/guacamole/src/main/frontend/src/app/client/templates/client.html b/guacamole/src/main/frontend/src/app/client/templates/client.html index 66dca3a770..efd16f4f7d 100644 --- a/guacamole/src/main/frontend/src/app/client/templates/client.html +++ b/guacamole/src/main/frontend/src/app/client/templates/client.html @@ -128,6 +128,14 @@

{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}

model-only="true"> + + +