@@ -2853,41 +2853,9 @@ sub app_html {
|
||
| 2853 | 2853 |
.vhost-host-select { width: 100%; max-width: 100%; min-height: 34px; }
|
| 2854 | 2854 |
.vhost-inline-editor { display: grid; grid-template-columns: minmax(260px, 1fr) minmax(260px, 1fr) auto; gap: 8px; padding: 10px; border-bottom: 1px solid var(--line); background: #fff; }
|
| 2855 | 2855 |
.vhost-delete { color: var(--bad); }
|
| 2856 |
- .modal-backdrop {
|
|
| 2857 |
- position: fixed; |
|
| 2858 |
- inset: 0; |
|
| 2859 |
- z-index: 10; |
|
| 2860 |
- display: grid; |
|
| 2861 |
- align-items: start; |
|
| 2862 |
- justify-items: center; |
|
| 2863 |
- padding: 72px 16px 24px; |
|
| 2864 |
- background: rgba(21,32,51,.48); |
|
| 2865 |
- overflow: auto; |
|
| 2866 |
- } |
|
| 2867 |
- .modal-backdrop[hidden] { display: none; }
|
|
| 2868 |
- .modal {
|
|
| 2869 |
- width: min(840px, 100%); |
|
| 2870 |
- max-height: calc(100dvh - 96px); |
|
| 2871 |
- overflow: auto; |
|
| 2872 |
- background: var(--panel); |
|
| 2873 |
- border: 1px solid var(--line); |
|
| 2874 |
- border-radius: 8px; |
|
| 2875 |
- box-shadow: 0 20px 60px rgba(21,32,51,.26); |
|
| 2876 |
- } |
|
| 2877 |
- .modal-head {
|
|
| 2878 |
- position: sticky; |
|
| 2879 |
- top: 0; |
|
| 2880 |
- z-index: 1; |
|
| 2881 |
- display: flex; |
|
| 2882 |
- align-items: center; |
|
| 2883 |
- justify-content: space-between; |
|
| 2884 |
- gap: 12px; |
|
| 2885 |
- padding: 12px 14px; |
|
| 2886 |
- border-bottom: 1px solid var(--line); |
|
| 2887 |
- background: #fafbfc; |
|
| 2888 |
- } |
|
| 2889 |
- .modal-head h2 { margin: 0; font-size: 14px; }
|
|
| 2890 |
- .modal-close { min-width: 34px; justify-content: center; padding: 7px; }
|
|
| 2856 |
+ .host-editor-panel { margin-top: 16px; }
|
|
| 2857 |
+ .host-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
| 2858 |
+ .host-editor-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
| 2891 | 2859 |
.form-message { min-height: 18px; color: var(--muted); font-size: 13px; }
|
| 2892 | 2860 |
.form-message.error { color: var(--bad); }
|
| 2893 | 2861 |
.form-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
@@ -2899,9 +2867,9 @@ sub app_html {
|
||
| 2899 | 2867 |
.host-tools { justify-content: flex-start; flex-wrap: wrap; }
|
| 2900 | 2868 |
.host-tools input { max-width: none; }
|
| 2901 | 2869 |
.vhost-inline-editor { grid-template-columns: 1fr; }
|
| 2870 |
+ .host-editor-head { align-items: stretch; flex-direction: column; }
|
|
| 2871 |
+ .host-editor-tools { justify-content: flex-start; }
|
|
| 2902 | 2872 |
.debug-controls { align-items: stretch; }
|
| 2903 |
- .modal-backdrop { padding-top: 16px; }
|
|
| 2904 |
- .modal { max-height: calc(100dvh - 32px); }
|
|
| 2905 | 2873 |
.grid { grid-template-columns: 1fr; }
|
| 2906 | 2874 |
table { min-width: 760px; }
|
| 2907 | 2875 |
.table-wrap { overflow-x: auto; }
|
@@ -3001,6 +2969,30 @@ sub app_html {
|
||
| 3001 | 2969 |
</table> |
| 3002 | 2970 |
</div> |
| 3003 | 2971 |
</section> |
| 2972 |
+ <section class="panel host-editor-panel"> |
|
| 2973 |
+ <div class="panel-head host-editor-head"> |
|
| 2974 |
+ <h2 id="host-form-title">New host</h2> |
|
| 2975 |
+ <div class="host-editor-tools"> |
|
| 2976 |
+ <button type="button" id="reset-host-form">Reset</button> |
|
| 2977 |
+ </div> |
|
| 2978 |
+ </div> |
|
| 2979 |
+ <form id="host-form" class="grid"> |
|
| 2980 |
+ <label>ID<input name="id" required></label> |
|
| 2981 |
+ <label>FQDN<input name="fqdn" required></label> |
|
| 2982 |
+ <label>Status<select name="status"><option>active</option><option>planned</option><option>retired</option></select></label> |
|
| 2983 |
+ <label>IP<input name="ip" required></label> |
|
| 2984 |
+ <label class="span2">Aliases<textarea name="aliases"></textarea></label> |
|
| 2985 |
+ <label>Roles<input name="roles"></label> |
|
| 2986 |
+ <label>Sources<input name="sources"></label> |
|
| 2987 |
+ <label>Monitoring<select name="monitoring"><option>pending</option><option>enabled</option><option>disabled</option></select></label> |
|
| 2988 |
+ <label>Notes<input name="notes"></label> |
|
| 2989 |
+ <div id="host-form-message" class="span2 form-message" aria-live="polite"></div> |
|
| 2990 |
+ <div class="span2 form-actions"> |
|
| 2991 |
+ <button class="primary" type="submit" id="save-host">Save host</button> |
|
| 2992 |
+ <button class="danger" type="button" id="delete-host">Delete host</button> |
|
| 2993 |
+ </div> |
|
| 2994 |
+ </form> |
|
| 2995 |
+ </section> |
|
| 3004 | 2996 |
</section> |
| 3005 | 2997 |
|
| 3006 | 2998 |
<section class="page" id="page-vhosts" data-page="vhosts" hidden> |
@@ -3136,30 +3128,6 @@ sub app_html {
|
||
| 3136 | 3128 |
</section> |
| 3137 | 3129 |
</main> |
| 3138 | 3130 |
|
| 3139 |
- <div id="host-modal" class="modal-backdrop" hidden> |
|
| 3140 |
- <section class="modal" role="dialog" aria-modal="true" aria-labelledby="host-modal-title"> |
|
| 3141 |
- <div class="modal-head"> |
|
| 3142 |
- <h2 id="host-modal-title">Edit host</h2> |
|
| 3143 |
- <button type="button" id="close-host-modal" class="modal-close" aria-label="Close host editor">x</button> |
|
| 3144 |
- </div> |
|
| 3145 |
- <form id="host-form" class="grid"> |
|
| 3146 |
- <label>ID<input name="id" required></label> |
|
| 3147 |
- <label>FQDN<input name="fqdn" required></label> |
|
| 3148 |
- <label>Status<select name="status"><option>active</option><option>planned</option><option>retired</option></select></label> |
|
| 3149 |
- <label>IP<input name="ip" required></label> |
|
| 3150 |
- <label class="span2">Aliases<textarea name="aliases"></textarea></label> |
|
| 3151 |
- <label>Roles<input name="roles"></label> |
|
| 3152 |
- <label>Sources<input name="sources"></label> |
|
| 3153 |
- <label>Monitoring<select name="monitoring"><option>pending</option><option>enabled</option><option>disabled</option></select></label> |
|
| 3154 |
- <label>Notes<input name="notes"></label> |
|
| 3155 |
- <div id="host-form-message" class="span2 form-message" aria-live="polite"></div> |
|
| 3156 |
- <div class="span2 form-actions"> |
|
| 3157 |
- <button class="primary" type="submit" id="save-host">Save host</button> |
|
| 3158 |
- <button class="danger" type="button" id="delete-host">Delete host</button> |
|
| 3159 |
- </div> |
|
| 3160 |
- </form> |
|
| 3161 |
- </section> |
|
| 3162 |
- </div> |
|
| 3163 | 3131 |
</div> |
| 3164 | 3132 |
|
| 3165 | 3133 |
<div class="build-control" title="Running build __HOST_MANAGER_BUILD_TITLE__"> |
@@ -3170,6 +3138,8 @@ sub app_html {
|
||
| 3170 | 3138 |
<script> |
| 3171 | 3139 |
let state = { hosts: [], problems: [], workOrders: [], authenticated: false, debugTable: '' };
|
| 3172 | 3140 |
let hostFormSnapshot = ''; |
| 3141 |
+ let hostFormBusy = false; |
|
| 3142 |
+ let hostFormMode = 'new'; |
|
| 3173 | 3143 |
|
| 3174 | 3144 |
const $ = (id) => document.getElementById(id); |
| 3175 | 3145 |
const msg = (text) => { $('message').textContent = text || ''; };
|
@@ -3777,45 +3747,36 @@ sub app_html {
|
||
| 3777 | 3747 |
if (!await ensureAuthenticated('Autentifica-te inainte de editare.')) return;
|
| 3778 | 3748 |
const host = state.hosts.find(h => h.id === id); |
| 3779 | 3749 |
if (!host) return; |
| 3780 |
- const form = $('host-form');
|
|
| 3781 | 3750 |
clearHostFormMessage(); |
| 3782 | 3751 |
for (const key of ['id', 'fqdn', 'status', 'ip', 'monitoring', 'notes']) hostField(key).value = host[key] || ''; |
| 3783 | 3752 |
hostField('aliases').value = (host.aliases || []).join('\n');
|
| 3784 | 3753 |
hostField('roles').value = (host.roles || []).join(' ');
|
| 3785 | 3754 |
hostField('sources').value = (host.sources || []).join(' ');
|
| 3786 |
- openHostModal('Edit host');
|
|
| 3755 |
+ activateHostForm('Edit host', 'edit', 'fqdn');
|
|
| 3787 | 3756 |
} |
| 3788 | 3757 |
|
| 3789 | 3758 |
async function newHost() {
|
| 3790 | 3759 |
if (!await ensureAuthenticated('Autentifica-te inainte de adaugarea unui host.')) return;
|
| 3791 |
- const form = $('host-form');
|
|
| 3792 |
- form.reset(); |
|
| 3793 |
- clearHostFormMessage(); |
|
| 3794 |
- hostField('status').value = 'active';
|
|
| 3795 |
- hostField('monitoring').value = 'pending';
|
|
| 3796 |
- openHostModal('New host');
|
|
| 3760 |
+ resetHostForm(false, true); |
|
| 3797 | 3761 |
} |
| 3798 | 3762 |
|
| 3799 |
- function openHostModal(title) {
|
|
| 3800 |
- $('host-modal-title').textContent = title || 'Edit host';
|
|
| 3801 |
- $('host-modal').hidden = false;
|
|
| 3802 |
- document.body.style.overflow = 'hidden'; |
|
| 3763 |
+ function activateHostForm(title, mode, focusField = 'id', scroll = true) {
|
|
| 3764 |
+ hostFormMode = mode || 'new'; |
|
| 3765 |
+ $('host-form-title').textContent = title || 'New host';
|
|
| 3803 | 3766 |
hostFormSnapshot = hostFormState(); |
| 3804 |
- hostField('id').focus();
|
|
| 3767 |
+ syncHostFormActions(); |
|
| 3768 |
+ if (scroll) $('host-form').scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
| 3769 |
+ hostField(focusField).focus(); |
|
| 3805 | 3770 |
} |
| 3806 | 3771 |
|
| 3807 |
- function requestCloseHostModal() {
|
|
| 3808 |
- if ($('save-host').disabled) return;
|
|
| 3809 |
- if (hostFormDirty() && !confirm('Discard unsaved host changes?')) return;
|
|
| 3810 |
- closeHostModal(); |
|
| 3811 |
- } |
|
| 3812 |
- |
|
| 3813 |
- function closeHostModal() {
|
|
| 3814 |
- $('host-modal').hidden = true;
|
|
| 3815 |
- document.body.style.overflow = ''; |
|
| 3816 |
- setHostFormBusy(false); |
|
| 3772 |
+ function resetHostForm(force = false, scroll = false) {
|
|
| 3773 |
+ if (hostFormBusy && !force) return; |
|
| 3774 |
+ if (!force && hostFormDirty() && !confirm('Discard unsaved host changes?')) return;
|
|
| 3775 |
+ $('host-form').reset();
|
|
| 3817 | 3776 |
clearHostFormMessage(); |
| 3818 |
- hostFormSnapshot = ''; |
|
| 3777 |
+ hostField('status').value = 'active';
|
|
| 3778 |
+ hostField('monitoring').value = 'pending';
|
|
| 3779 |
+ activateHostForm('New host', 'new', 'id', scroll);
|
|
| 3819 | 3780 |
} |
| 3820 | 3781 |
|
| 3821 | 3782 |
function hostField(name) {
|
@@ -3827,13 +3788,18 @@ sub app_html {
|
||
| 3827 | 3788 |
} |
| 3828 | 3789 |
|
| 3829 | 3790 |
function hostFormDirty() {
|
| 3830 |
- return !$('host-modal').hidden && hostFormSnapshot && hostFormState() !== hostFormSnapshot;
|
|
| 3791 |
+ return !!hostFormSnapshot && hostFormState() !== hostFormSnapshot; |
|
| 3831 | 3792 |
} |
| 3832 | 3793 |
|
| 3833 | 3794 |
function setHostFormBusy(busy) {
|
| 3834 |
- $('save-host').disabled = busy;
|
|
| 3835 |
- $('delete-host').disabled = busy;
|
|
| 3836 |
- $('close-host-modal').disabled = busy;
|
|
| 3795 |
+ hostFormBusy = !!busy; |
|
| 3796 |
+ syncHostFormActions(); |
|
| 3797 |
+ } |
|
| 3798 |
+ |
|
| 3799 |
+ function syncHostFormActions() {
|
|
| 3800 |
+ $('save-host').disabled = hostFormBusy;
|
|
| 3801 |
+ $('delete-host').disabled = hostFormBusy || hostFormMode !== 'edit';
|
|
| 3802 |
+ $('reset-host-form').disabled = hostFormBusy;
|
|
| 3837 | 3803 |
} |
| 3838 | 3804 |
|
| 3839 | 3805 |
function setHostFormMessage(text, isError = false) {
|
@@ -4049,13 +4015,7 @@ sub app_html {
|
||
| 4049 | 4015 |
$('debug-db-refresh').addEventListener('click', () => renderDebugDatabase().catch(e => {
|
| 4050 | 4016 |
if (!isAuthLost(e)) msg(e.message); |
| 4051 | 4017 |
})); |
| 4052 |
- $('close-host-modal').addEventListener('click', requestCloseHostModal);
|
|
| 4053 |
- $('host-modal').addEventListener('click', (event) => {
|
|
| 4054 |
- if (event.target === $('host-modal') && !$('save-host').disabled) closeHostModal();
|
|
| 4055 |
- }); |
|
| 4056 |
- window.addEventListener('keydown', (event) => {
|
|
| 4057 |
- if (event.key === 'Escape' && !$('host-modal').hidden) requestCloseHostModal();
|
|
| 4058 |
- }); |
|
| 4018 |
+ $('reset-host-form').addEventListener('click', () => resetHostForm());
|
|
| 4059 | 4019 |
|
| 4060 | 4020 |
$('host-form').addEventListener('submit', async (event) => {
|
| 4061 | 4021 |
event.preventDefault(); |
@@ -4063,11 +4023,21 @@ sub app_html {
|
||
| 4063 | 4023 |
setHostFormBusy(true); |
| 4064 | 4024 |
setHostFormMessage('Saving...');
|
| 4065 | 4025 |
try {
|
| 4026 |
+ const savedId = hostField('id').value;
|
|
| 4066 | 4027 |
await api('/api/hosts/upsert', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formObject(event.target)) });
|
| 4067 |
- hostFormSnapshot = hostFormState(); |
|
| 4068 |
- closeHostModal(); |
|
| 4069 | 4028 |
msg('host saved');
|
| 4070 | 4029 |
await refresh(); |
| 4030 |
+ const host = state.hosts.find(entry => entry.id === savedId); |
|
| 4031 |
+ if (host) {
|
|
| 4032 |
+ clearHostFormMessage(); |
|
| 4033 |
+ for (const key of ['id', 'fqdn', 'status', 'ip', 'monitoring', 'notes']) hostField(key).value = host[key] || ''; |
|
| 4034 |
+ hostField('aliases').value = (host.aliases || []).join('\n');
|
|
| 4035 |
+ hostField('roles').value = (host.roles || []).join(' ');
|
|
| 4036 |
+ hostField('sources').value = (host.sources || []).join(' ');
|
|
| 4037 |
+ activateHostForm('Edit host', 'edit', 'fqdn', false);
|
|
| 4038 |
+ } else {
|
|
| 4039 |
+ resetHostForm(true, false); |
|
| 4040 |
+ } |
|
| 4071 | 4041 |
} catch (e) {
|
| 4072 | 4042 |
if (isAuthLost(e)) return; |
| 4073 | 4043 |
setHostFormMessage(e.message, true); |
@@ -4092,11 +4062,9 @@ sub app_html {
|
||
| 4092 | 4062 |
setHostFormMessage('Deleting...');
|
| 4093 | 4063 |
try {
|
| 4094 | 4064 |
await api('/api/hosts/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }) });
|
| 4095 |
- $('host-form').reset();
|
|
| 4096 |
- hostFormSnapshot = hostFormState(); |
|
| 4097 |
- closeHostModal(); |
|
| 4098 | 4065 |
msg('host deleted');
|
| 4099 | 4066 |
await refresh(); |
| 4067 |
+ resetHostForm(true, false); |
|
| 4100 | 4068 |
} catch (e) {
|
| 4101 | 4069 |
if (isAuthLost(e)) return; |
| 4102 | 4070 |
setHostFormMessage(e.message, true); |
@@ -4106,6 +4074,8 @@ sub app_html {
|
||
| 4106 | 4074 |
} |
| 4107 | 4075 |
}); |
| 4108 | 4076 |
|
| 4077 |
+ resetHostForm(true, false); |
|
| 4078 |
+ |
|
| 4109 | 4079 |
$('write-tsv').addEventListener('click', async () => {
|
| 4110 | 4080 |
if (!confirm('Write config/local-hosts.tsv from the runtime registry?')) return;
|
| 4111 | 4081 |
try {
|