feat(streamlit): Add domain column with editing in dashboard
- Show domain column with editable input for non-exposed instances - Show clickable domain link + edit button for exposed instances - Add editDomain modal for changing domain on exposed instances - Domain input pre-filled with default (id.gk2.secubox.in) - Separate Status column for SSL/WAF badges - Update API to support domain parameter in renameInstance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
93fcefa5c3
commit
80c496b57e
@ -195,7 +195,7 @@ var callRenameApp = rpc.declare({
|
|||||||
var callRenameInstance = rpc.declare({
|
var callRenameInstance = rpc.declare({
|
||||||
object: 'luci.streamlit',
|
object: 'luci.streamlit',
|
||||||
method: 'rename_instance',
|
method: 'rename_instance',
|
||||||
params: ['id', 'name'],
|
params: ['id', 'name', 'domain'],
|
||||||
expect: { result: {} }
|
expect: { result: {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -457,8 +457,8 @@ return baseclass.extend({
|
|||||||
return callRenameApp(id, name);
|
return callRenameApp(id, name);
|
||||||
},
|
},
|
||||||
|
|
||||||
renameInstance: function(id, name) {
|
renameInstance: function(id, name, domain) {
|
||||||
return callRenameInstance(id, name);
|
return callRenameInstance(id, name, domain || '');
|
||||||
},
|
},
|
||||||
|
|
||||||
getGiteaConfig: function() {
|
getGiteaConfig: function() {
|
||||||
|
|||||||
@ -147,33 +147,64 @@ return view.extend({
|
|||||||
var certValid = exp.cert_valid;
|
var certValid = exp.cert_valid;
|
||||||
var authRequired = exp.auth_required;
|
var authRequired = exp.auth_required;
|
||||||
var wafEnabled = exp.waf_enabled;
|
var wafEnabled = exp.waf_enabled;
|
||||||
|
var domain = exp.domain || '';
|
||||||
// Status indicator
|
|
||||||
var statusBadge;
|
|
||||||
if (isExposed && certValid) {
|
|
||||||
statusBadge = E('span', { 'style': 'background:#0a0; color:#fff; padding:2px 6px; border-radius:3px; font-size:11px' },
|
|
||||||
'\u2713 ' + (exp.domain || 'Exposed'));
|
|
||||||
} else if (isExposed) {
|
|
||||||
statusBadge = E('span', { 'style': 'background:#f90; color:#fff; padding:2px 6px; border-radius:3px; font-size:11px' },
|
|
||||||
'\u26A0 Cert pending');
|
|
||||||
} else {
|
|
||||||
statusBadge = E('span', { 'style': 'color:#999' }, _('Local only'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// WAF badge (shown when exposed)
|
|
||||||
var wafBadge = '';
|
|
||||||
if (isExposed && wafEnabled) {
|
|
||||||
wafBadge = E('span', {
|
|
||||||
'style': 'display:inline-block; padding:2px 6px; border-radius:4px; font-size:0.85em; background:#d1ecf1; color:#0c5460; margin-left:4px',
|
|
||||||
'title': _('Traffic inspected by WAF (mitmproxy)')
|
|
||||||
}, 'WAF');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Running indicator
|
// Running indicator
|
||||||
var runStatus = inst.enabled ?
|
var runStatus = inst.enabled ?
|
||||||
E('span', { 'style': 'color:#0a0' }, '\u25CF') :
|
E('span', { 'style': 'color:#0a0' }, '\u25CF') :
|
||||||
E('span', { 'style': 'color:#999' }, '\u25CB');
|
E('span', { 'style': 'color:#999' }, '\u25CB');
|
||||||
|
|
||||||
|
// Domain/Vhost column - editable input or link
|
||||||
|
var domainCell;
|
||||||
|
if (isExposed && domain) {
|
||||||
|
// Show clickable link + edit button
|
||||||
|
domainCell = E('div', { 'style': 'display:flex; align-items:center; gap:4px' }, [
|
||||||
|
E('a', {
|
||||||
|
'href': 'https://' + domain,
|
||||||
|
'target': '_blank',
|
||||||
|
'style': 'font-size:12px; color:#007bff'
|
||||||
|
}, domain),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button',
|
||||||
|
'style': 'padding:2px 6px; font-size:10px',
|
||||||
|
'title': _('Edit domain'),
|
||||||
|
'click': function() { self.editDomain(inst.id, domain); }
|
||||||
|
}, '\u270E')
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Show input field for setting domain before expose
|
||||||
|
var inputId = 'domain-' + inst.id;
|
||||||
|
var defaultDomain = inst.id + '.gk2.secubox.in';
|
||||||
|
domainCell = E('input', {
|
||||||
|
'type': 'text',
|
||||||
|
'id': inputId,
|
||||||
|
'value': defaultDomain,
|
||||||
|
'style': 'width:160px; font-size:11px; padding:2px 4px',
|
||||||
|
'placeholder': 'domain.example.com'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status badges
|
||||||
|
var badges = [];
|
||||||
|
if (isExposed) {
|
||||||
|
if (certValid) {
|
||||||
|
badges.push(E('span', {
|
||||||
|
'style': 'background:#0a0; color:#fff; padding:2px 6px; border-radius:3px; font-size:10px'
|
||||||
|
}, '\u2713 SSL'));
|
||||||
|
} else {
|
||||||
|
badges.push(E('span', {
|
||||||
|
'style': 'background:#f90; color:#fff; padding:2px 6px; border-radius:3px; font-size:10px'
|
||||||
|
}, '\u26A0 Cert'));
|
||||||
|
}
|
||||||
|
if (wafEnabled) {
|
||||||
|
badges.push(E('span', {
|
||||||
|
'style': 'background:#d1ecf1; color:#0c5460; padding:2px 6px; border-radius:3px; font-size:10px; margin-left:2px'
|
||||||
|
}, 'WAF'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
badges.push(E('span', { 'style': 'color:#999; font-size:11px' }, _('Local')));
|
||||||
|
}
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
var actions = [];
|
var actions = [];
|
||||||
|
|
||||||
@ -204,8 +235,12 @@ return view.extend({
|
|||||||
actions.push(E('button', {
|
actions.push(E('button', {
|
||||||
'class': 'cbi-button cbi-button-positive',
|
'class': 'cbi-button cbi-button-positive',
|
||||||
'style': 'margin-left:4px',
|
'style': 'margin-left:4px',
|
||||||
'title': _('Expose (one-click)'),
|
'title': _('Expose with domain'),
|
||||||
'click': function() { self.exposeInstance(inst.id); }
|
'click': function() {
|
||||||
|
var input = document.getElementById('domain-' + inst.id);
|
||||||
|
var customDomain = input ? input.value.trim() : '';
|
||||||
|
self.exposeInstance(inst.id, customDomain);
|
||||||
|
}
|
||||||
}, '\u2197'));
|
}, '\u2197'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +266,8 @@ return view.extend({
|
|||||||
E('td', {}, [runStatus, ' ', E('strong', {}, inst.id)]),
|
E('td', {}, [runStatus, ' ', E('strong', {}, inst.id)]),
|
||||||
E('td', {}, inst.app || '-'),
|
E('td', {}, inst.app || '-'),
|
||||||
E('td', {}, ':' + inst.port),
|
E('td', {}, ':' + inst.port),
|
||||||
E('td', {}, [statusBadge, wafBadge]),
|
E('td', {}, domainCell),
|
||||||
|
E('td', {}, badges),
|
||||||
E('td', {}, actions)
|
E('td', {}, actions)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@ -241,7 +277,8 @@ return view.extend({
|
|||||||
E('th', { 'class': 'th' }, _('Instance')),
|
E('th', { 'class': 'th' }, _('Instance')),
|
||||||
E('th', { 'class': 'th' }, _('App')),
|
E('th', { 'class': 'th' }, _('App')),
|
||||||
E('th', { 'class': 'th' }, _('Port')),
|
E('th', { 'class': 'th' }, _('Port')),
|
||||||
E('th', { 'class': 'th' }, _('Exposure')),
|
E('th', { 'class': 'th' }, _('Domain')),
|
||||||
|
E('th', { 'class': 'th' }, _('Status')),
|
||||||
E('th', { 'class': 'th' }, _('Actions'))
|
E('th', { 'class': 'th' }, _('Actions'))
|
||||||
])
|
])
|
||||||
].concat(rows));
|
].concat(rows));
|
||||||
@ -473,14 +510,15 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// One-click expose
|
// One-click expose with optional domain
|
||||||
exposeInstance: function(id) {
|
exposeInstance: function(id, domain) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
domain = domain || '';
|
||||||
ui.showModal(_('Exposing...'), [
|
ui.showModal(_('Exposing...'), [
|
||||||
E('p', { 'class': 'spinning' }, _('Creating vhost + SSL certificate...'))
|
E('p', { 'class': 'spinning' }, _('Creating vhost + SSL certificate for ') + (domain || id + '.gk2.secubox.in') + '...')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.emancipateInstance(id, '').then(function(r) {
|
api.emancipateInstance(id, domain).then(function(r) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (r && r.success) {
|
if (r && r.success) {
|
||||||
ui.addNotification(null, E('p', {}, _('Exposed at: ') + r.url), 'success');
|
ui.addNotification(null, E('p', {}, _('Exposed at: ') + r.url), 'success');
|
||||||
@ -491,6 +529,49 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Edit domain for existing exposed instance
|
||||||
|
editDomain: function(id, currentDomain) {
|
||||||
|
var self = this;
|
||||||
|
ui.showModal(_('Edit Domain'), [
|
||||||
|
E('p', {}, _('Change domain for instance: ') + id),
|
||||||
|
E('input', {
|
||||||
|
'type': 'text',
|
||||||
|
'id': 'edit-domain-input',
|
||||||
|
'value': currentDomain,
|
||||||
|
'style': 'width:100%; margin:8px 0'
|
||||||
|
}),
|
||||||
|
E('p', { 'style': 'color:#666; font-size:12px' },
|
||||||
|
_('Note: Changing domain will request a new SSL certificate.')),
|
||||||
|
E('div', { 'class': 'right', 'style': 'margin-top:16px' }, [
|
||||||
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button cbi-button-positive',
|
||||||
|
'style': 'margin-left:8px',
|
||||||
|
'click': function() {
|
||||||
|
var newDomain = document.getElementById('edit-domain-input').value.trim();
|
||||||
|
if (!newDomain) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Domain cannot be empty')), 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.hideModal();
|
||||||
|
ui.showModal(_('Updating...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Updating domain and certificate...'))
|
||||||
|
]);
|
||||||
|
api.renameInstance(id, id, newDomain).then(function(r) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (r && r.success) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Domain updated to: ') + newDomain), 'success');
|
||||||
|
self.refresh().then(function() { self.updateStatus(); });
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', {}, r.message || _('Failed')), 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, _('Save'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
// Unpublish
|
// Unpublish
|
||||||
unpublishInstance: function(id, domain) {
|
unpublishInstance: function(id, domain) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user