Showing 3 changed files with 69 additions and 11 deletions
+2 -0
deploy/jumper/host-manager.service
@@ -9,6 +9,8 @@ User=host-manager
9 9
 Group=host-manager
10 10
 WorkingDirectory=/usr/local/xdev-host-manager
11 11
 EnvironmentFile=/etc/xdev/host-manager.env
12
+ExecStartPre=+/usr/bin/install -d -o host-manager -g host-manager -m 0755 /usr/local/xdev-host-manager/var /usr/local/xdev-host-manager/backups
13
+ExecStartPre=+/usr/bin/chown -R host-manager:host-manager /usr/local/xdev-host-manager/var /usr/local/xdev-host-manager/backups
12 14
 ExecStart=/usr/bin/perl /usr/local/xdev-host-manager/scripts/host_manager.pl --bind 127.0.0.1 --port 8088
13 15
 Restart=on-failure
14 16
 RestartSec=3
+1 -0
scripts/deploy_to_jumper.sh
@@ -96,6 +96,7 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
96 96
 fi
97 97
 
98 98
 ssh "$TARGET_HOST" "mkdir -p '$TARGET_DIR/scripts' '$TARGET_DIR/deploy' '$TARGET_DIR/.doc' '$TARGET_DIR/var'"
99
+ssh "$TARGET_HOST" "sudo -n install -d -o host-manager -g host-manager -m 0755 '$TARGET_DIR/var' '$TARGET_DIR/backups'"
99 100
 rsync "${rsync_args[@]}" scripts/ "$TARGET_HOST:$TARGET_DIR/scripts/"
100 101
 rsync "${rsync_args[@]}" deploy/ "$TARGET_HOST:$TARGET_DIR/deploy/"
101 102
 rsync "${rsync_args[@]}" .doc/ "$TARGET_HOST:$TARGET_DIR/.doc/"
+66 -11
scripts/host_manager.pl
@@ -2756,10 +2756,39 @@ sub app_html {
2756 2756
       '/debug': 'debug',
2757 2757
     };
2758 2758
 
2759
+    function isAuthLost(error) {
2760
+      return !!(error && error.authLost);
2761
+    }
2762
+
2763
+    function authLostError(message) {
2764
+      const error = new Error(message || 'Sesiunea a expirat. Autentifica-te din nou.');
2765
+      error.authLost = true;
2766
+      return error;
2767
+    }
2768
+
2769
+    function handleAuthLost(message) {
2770
+      state.authenticated = false;
2771
+      msg('');
2772
+      showLogin(message || 'Sesiunea a expirat. Autentifica-te din nou.');
2773
+    }
2774
+
2759 2775
     async function api(path, options = {}) {
2760 2776
       const res = await fetch(path, options);
2761
-      const body = await res.json();
2762
-      if (!res.ok) throw new Error(body.error || res.statusText);
2777
+      let body = {};
2778
+      try {
2779
+        body = await res.json();
2780
+      } catch (_) {
2781
+        body = {};
2782
+      }
2783
+      const errorCode = body.error || '';
2784
+      if (!res.ok) {
2785
+        if (res.status === 401 && !(path === '/api/login' && errorCode === 'invalid_otp')) {
2786
+          const error = authLostError();
2787
+          handleAuthLost(error.message);
2788
+          throw error;
2789
+        }
2790
+        throw new Error(errorCode || res.statusText);
2791
+      }
2763 2792
       return body;
2764 2793
     }
2765 2794
 
@@ -2781,11 +2810,14 @@ sub app_html {
2781 2810
         history.pushState({ page: target }, '', href);
2782 2811
       }
2783 2812
       if (state.authenticated && target === 'debug') {
2784
-        renderDebugDatabase().catch(e => msg(e.message));
2813
+        renderDebugDatabase().catch(e => {
2814
+          if (!isAuthLost(e)) msg(e.message);
2815
+        });
2785 2816
       }
2786 2817
     }
2787 2818
 
2788 2819
     function showLogin(errorText) {
2820
+      state.authenticated = false;
2789 2821
       document.body.classList.remove('is-app');
2790 2822
       document.body.classList.add('is-login');
2791 2823
       $('app').style.display = 'none';
@@ -2805,7 +2837,7 @@ sub app_html {
2805 2837
     async function refresh() {
2806 2838
       const session = await api('/api/session');
2807 2839
       state.authenticated = session.authenticated;
2808
-      if (!state.authenticated) { showLogin(); return; }
2840
+      if (!state.authenticated) { showLogin('Autentifica-te pentru a continua.'); return; }
2809 2841
       showApp();
2810 2842
       const data = await api('/api/hosts');
2811 2843
       state.hosts = data.hosts || [];
@@ -2884,6 +2916,7 @@ sub app_html {
2884 2916
           </tr>`;
2885 2917
         }).join('') : '<tr><td colspan="6" class="muted">No issued certificates.</td></tr>';
2886 2918
       } catch (e) {
2919
+        if (isAuthLost(e)) return;
2887 2920
         $('ca-status').innerHTML = `<div class="problem"><strong>CA status unavailable</strong> ${escapeHtml(e.message)}</div>`;
2888 2921
         $('ca-certs-summary').innerHTML = '';
2889 2922
         $('ca-certs').innerHTML = '<tr><td colspan="6" class="muted">Certificate list unavailable.</td></tr>';
@@ -2958,6 +2991,7 @@ sub app_html {
2958 2991
         document.querySelectorAll('[data-wo-checklist]').forEach(input => input.addEventListener('change', () => updateWorkOrderChecklist(input.dataset.woChecklist, input.dataset.itemId, input.checked)));
2959 2992
         document.querySelectorAll('[data-confirm-wo]').forEach(button => button.addEventListener('click', () => confirmWorkOrder(button.dataset.confirmWo)));
2960 2993
       } catch (e) {
2994
+        if (isAuthLost(e)) return;
2961 2995
         $('work-orders').innerHTML = `<div class="problem"><strong>Work orders unavailable</strong> ${escapeHtml(e.message)}</div>`;
2962 2996
       }
2963 2997
     }
@@ -3030,7 +3064,13 @@ sub app_html {
3030 3064
         });
3031 3065
         msg('work order updated');
3032 3066
         await refresh();
3033
-      } catch (e) { msg(e.message); await refresh(); }
3067
+      } catch (e) {
3068
+        if (isAuthLost(e)) return;
3069
+        msg(e.message);
3070
+        await refresh().catch(refreshError => {
3071
+          if (!isAuthLost(refreshError)) msg(refreshError.message);
3072
+        });
3073
+      }
3034 3074
     }
3035 3075
 
3036 3076
     async function confirmWorkOrder(id) {
@@ -3044,7 +3084,10 @@ sub app_html {
3044 3084
         });
3045 3085
         msg('work order confirmed; local-hosts.tsv written');
3046 3086
         await refresh();
3047
-      } catch (e) { msg(e.message); }
3087
+      } catch (e) {
3088
+        if (isAuthLost(e)) return;
3089
+        msg(e.message);
3090
+      }
3048 3091
     }
3049 3092
 
3050 3093
     function renderHosts() {
@@ -3321,11 +3364,17 @@ sub app_html {
3321 3364
       window.location.replace('/?logged_out=' + Date.now());
3322 3365
     });
3323 3366
 
3324
-    $('refresh').addEventListener('click', () => refresh().catch(e => msg(e.message)));
3367
+    $('refresh').addEventListener('click', () => refresh().catch(e => {
3368
+      if (!isAuthLost(e)) msg(e.message);
3369
+    }));
3325 3370
     $('filter').addEventListener('input', renderHosts);
3326 3371
     $('new-host').addEventListener('click', newHost);
3327
-    $('debug-db-refresh').addEventListener('click', () => renderDebugDatabase().catch(e => msg(e.message)));
3328
-    $('debug-db-table').addEventListener('change', () => renderDebugTable($('debug-db-table').value).catch(e => msg(e.message)));
3372
+    $('debug-db-refresh').addEventListener('click', () => renderDebugDatabase().catch(e => {
3373
+      if (!isAuthLost(e)) msg(e.message);
3374
+    }));
3375
+    $('debug-db-table').addEventListener('change', () => renderDebugTable($('debug-db-table').value).catch(e => {
3376
+      if (!isAuthLost(e)) msg(e.message);
3377
+    }));
3329 3378
     $('close-host-modal').addEventListener('click', requestCloseHostModal);
3330 3379
     $('host-modal').addEventListener('click', (event) => {
3331 3380
       if (event.target === $('host-modal') && !$('save-host').disabled) closeHostModal();
@@ -3345,6 +3394,7 @@ sub app_html {
3345 3394
         msg('host saved');
3346 3395
         await refresh();
3347 3396
       } catch (e) {
3397
+        if (isAuthLost(e)) return;
3348 3398
         setHostFormMessage(e.message, true);
3349 3399
         msg(e.message);
3350 3400
       } finally {
@@ -3373,6 +3423,7 @@ sub app_html {
3373 3423
         msg('host deleted');
3374 3424
         await refresh();
3375 3425
       } catch (e) {
3426
+        if (isAuthLost(e)) return;
3376 3427
         setHostFormMessage(e.message, true);
3377 3428
         msg(e.message);
3378 3429
       } finally {
@@ -3385,10 +3436,14 @@ sub app_html {
3385 3436
       try {
3386 3437
         await api('/api/render/local-hosts-tsv', { method: 'POST' });
3387 3438
         msg('local-hosts.tsv written');
3388
-      } catch (e) { msg(e.message); }
3439
+      } catch (e) {
3440
+        if (!isAuthLost(e)) msg(e.message);
3441
+      }
3389 3442
     });
3390 3443
 
3391
-    refresh().catch(() => showLogin());
3444
+    refresh().catch(e => {
3445
+      if (!isAuthLost(e)) showLogin(e.message);
3446
+    });
3392 3447
   </script>
3393 3448
 </body>
3394 3449
 </html>