Showing 1 changed files with 71 additions and 101 deletions
+71 -101
scripts/host_manager.pl
@@ -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 {