diff --git a/public/scripts/components/NodeDetailsComponent.js b/public/scripts/components/NodeDetailsComponent.js
index 196518d..7d88f02 100644
--- a/public/scripts/components/NodeDetailsComponent.js
+++ b/public/scripts/components/NodeDetailsComponent.js
@@ -5,6 +5,28 @@ class NodeDetailsComponent extends Component {
this.suppressLoadingUI = false;
}
+ // Helper functions for color conversion
+ rgbIntToHex(rgbInt) {
+ if (!rgbInt && rgbInt !== 0) return '#000000';
+ const num = parseInt(rgbInt);
+ if (isNaN(num)) return '#000000';
+ const r = (num >> 16) & 255;
+ const g = (num >> 8) & 255;
+ const b = num & 255;
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
+ }
+
+ hexToRgbInt(hex) {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ if (!result) return 0;
+ const r = parseInt(result[1], 16);
+ const g = parseInt(result[2], 16);
+ const b = parseInt(result[3], 16);
+ return (r << 16) + (g << 8) + b;
+ }
+
+
+
setupViewModelListeners() {
this.subscribeToProperty('nodeStatus', this.handleNodeStatusUpdate.bind(this));
this.subscribeToProperty('tasks', this.handleTasksUpdate.bind(this));
@@ -421,7 +443,31 @@ class NodeDetailsComponent extends Component {
${p.name}${p.required ? ' *' : ''}
${ (Array.isArray(p.values) && p.values.length > 1)
? ``
- : ``
+ : (p.type === 'color')
+ ? `
+
+
+
`
+ : ``
}
`).join('')}`
@@ -486,13 +532,21 @@ class NodeDetailsComponent extends Component {
if (!formEl || !resultEl) return;
const inputs = Array.from(formEl.querySelectorAll('.param-input'));
- const params = inputs.map(input => ({
- name: input.dataset.paramName,
- location: input.dataset.paramLocation || 'body',
- type: input.dataset.paramType || 'string',
- required: input.dataset.paramRequired === '1',
- value: input.value
- }));
+ const params = inputs.map(input => {
+ let value = input.value;
+ // For color type, convert hex to RGB integer
+ if (input.dataset.paramType === 'color' && input.type === 'color') {
+ const rgbDisplay = input.parentElement.querySelector('.color-rgb-display');
+ value = rgbDisplay ? rgbDisplay.value : this.hexToRgbInt(input.value);
+ }
+ return {
+ name: input.dataset.paramName,
+ location: input.dataset.paramLocation || 'body',
+ type: input.dataset.paramType || 'string',
+ required: input.dataset.paramRequired === '1',
+ value: value
+ };
+ });
// Required validation
const missing = params.filter(p => p.required && (!p.value || String(p.value).trim() === ''));
@@ -528,6 +582,32 @@ class NodeDetailsComponent extends Component {
}
});
});
+
+ // Add event listeners for color pickers
+ const colorPickers = this.findAllElements('.color-picker');
+ colorPickers.forEach(colorInput => {
+ this.addEventListener(colorInput, 'input', (e) => {
+ const rgbDisplay = colorInput.parentElement.querySelector('.color-rgb-display');
+ if (rgbDisplay) {
+ const hexValue = e.target.value;
+ const rgbInt = this.hexToRgbInt(hexValue);
+ rgbDisplay.value = rgbInt;
+ }
+ });
+ });
+
+ // Update color picker when RGB display is manually changed (if we make it editable later)
+ const rgbDisplays = this.findAllElements('.color-rgb-display');
+ rgbDisplays.forEach(rgbInput => {
+ this.addEventListener(rgbInput, 'input', (e) => {
+ const colorPicker = rgbInput.parentElement.querySelector('.color-picker');
+ if (colorPicker) {
+ const rgbInt = parseInt(e.target.value) || 0;
+ const hexValue = this.rgbIntToHex(rgbInt);
+ colorPicker.value = hexValue;
+ }
+ });
+ });
}
escapeHtml(str) {
diff --git a/public/styles/main.css b/public/styles/main.css
index 8e1d7b8..3ec0bf1 100644
--- a/public/styles/main.css
+++ b/public/styles/main.css
@@ -3770,4 +3770,31 @@ html {
#cluster-members-container .error br {
display: none; /* tighten layout by avoiding forced line-breaks */
-}
\ No newline at end of file
+}
+/* Color picker styles */
+.color-input-container {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.color-picker {
+ width: 50px !important;
+ height: 35px !important;
+ padding: 0 !important;
+ border-radius: 6px !important;
+ cursor: pointer;
+}
+
+.color-rgb-display {
+ background: rgba(0, 0, 0, 0.3) !important;
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
+ color: rgba(255, 255, 255, 0.9) !important;
+ text-align: center;
+ font-family: 'Courier New', monospace !important;
+}
+
+.color-rgb-display:focus {
+ border-color: rgba(139, 92, 246, 0.5) !important;
+ background: rgba(139, 92, 246, 0.08) !important;
+}