signalk-czone 0.3.0-beta.17 → 0.3.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0-beta.18
4
+
5
+ - Adds in-page Signal K sign-in for browser and installed home-screen apps, without leaving CZone (#35, thanks @superstar76).
6
+ - Remembers login across app restarts, preserves the form during refreshes, and shows clear login errors with the Admin UI login retained as a fallback.
7
+
3
8
  ## 0.3.0-beta.17
4
9
 
5
10
  - Prompts for Signal K login when CZone circuit requests are unauthorized, returning to the webapp after sign-in (#34).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-czone",
3
- "version": "0.3.0-beta.17",
3
+ "version": "0.3.0-beta.18",
4
4
  "description": "BETA: Signal K CZone monitoring and control using dynamically parsed ZCF configuration and NMEA 2000",
5
5
  "main": "index.js",
6
6
  "keywords": [
package/public/index.html CHANGED
@@ -75,6 +75,12 @@
75
75
  .hero h1, .hero p { position:relative; z-index:1; }
76
76
  .hero h1 { margin:0; font-size:30px; line-height:1.15; letter-spacing:-.7px; }
77
77
  .hero p { margin:9px 0 0; color:#a9c2d9; font-size:14px; }
78
+ .login-form { display:grid; gap:8px; max-width:280px; margin:10px 0; }
79
+ .login-form input { padding:10px 12px; border-radius:10px; border:1px solid var(--line); background:rgba(255,255,255,.05); color:var(--text); font-size:16px; }
80
+ .login-form button { padding:10px 12px; border-radius:10px; border:0; background:linear-gradient(135deg,#168eea,#2164b8); color:#fff; font-weight:700; font-size:15px; cursor:pointer; }
81
+ .login-form button:disabled { opacity:.6; cursor:default; }
82
+ .login-form .login-error { color:#ff8a9a; font-size:13px; }
83
+ .login-form .login-error:empty { display:none; }
78
84
  .search { position:relative; }
79
85
  .search input { width:100%; border:1px solid var(--line); background:#0b1d30; color:white; border-radius:999px; padding:11px 15px 11px 38px; outline:none; }
80
86
  .search input:focus { border-color:rgba(56,169,255,.65); box-shadow:0 0 0 3px rgba(56,169,255,.10); }
@@ -702,13 +708,17 @@
702
708
  }
703
709
  function showLoadError(message, signIn) {
704
710
  const el = document.querySelector('#loadError');
711
+ // Keep a sign-in form that is already showing: someone may be typing in it.
712
+ if (signIn && !el.hidden && el.loginForm) return;
713
+ el.loginForm = null;
705
714
  el.textContent = message + ' ';
715
+ if (signIn) el.appendChild(loginForm(el));
706
716
  const link = document.createElement('a');
707
717
  link.style.color = 'var(--cyan)';
708
718
  if (signIn) {
709
719
  const here = window.location.pathname + window.location.search + window.location.hash;
710
720
  link.href = '/admin/#/login?redirect=' + encodeURIComponent(here);
711
- link.textContent = 'Sign in';
721
+ link.textContent = 'Or sign in on the Signal K login page';
712
722
  } else {
713
723
  link.href = window.location.href;
714
724
  link.textContent = 'Retry';
@@ -718,6 +728,46 @@
718
728
  el.hidden = false;
719
729
  }
720
730
 
731
+ // Sign in on the page itself. A home-screen app (iPad, Android) keeps its
732
+ // own login, separate from the browser's, and has no address bar; the link
733
+ // to the admin page stays as the alternative. "Remember me" keeps the login
734
+ // after the app is closed, for as long as Signal K's token lasts.
735
+ function loginForm(banner) {
736
+ const form = document.createElement('form');
737
+ form.className = 'login-form';
738
+ const field = (name, type, autocomplete, placeholder) => {
739
+ const input = document.createElement('input');
740
+ input.name = name; input.type = type; input.required = true;
741
+ input.autocomplete = autocomplete; input.placeholder = placeholder;
742
+ form.appendChild(input);
743
+ return input;
744
+ };
745
+ const user = field('username', 'text', 'username', 'Signal K user');
746
+ const pass = field('password', 'password', 'current-password', 'Password');
747
+ const button = document.createElement('button');
748
+ button.type = 'submit'; button.textContent = 'Log in';
749
+ form.appendChild(button);
750
+ const error = document.createElement('span');
751
+ error.className = 'login-error';
752
+ form.appendChild(error);
753
+ form.onsubmit = async event => {
754
+ event.preventDefault();
755
+ error.textContent = '';
756
+ button.disabled = true;
757
+ try {
758
+ const r = await fetch('/signalk/v1/auth/login', { method:'POST', credentials:'include', headers:{ 'Content-Type':'application/json' },
759
+ body: JSON.stringify({ username: user.value, password: pass.value, rememberMe: true }) });
760
+ if (r.ok) { window.location.reload(); return; }
761
+ error.textContent = r.status === 401 ? 'Wrong user name or password.' : 'Signal K refused the login (HTTP ' + r.status + ').';
762
+ } catch (_) {
763
+ error.textContent = 'Signal K could not be reached.';
764
+ }
765
+ button.disabled = false;
766
+ };
767
+ banner.loginForm = form;
768
+ return form;
769
+ }
770
+
721
771
  async function showCircuitRequestError(response) {
722
772
  let signIn = response.status === 401;
723
773
  if (response.status === 403) {
@@ -732,7 +782,8 @@
732
782
  ? 'Signal K denied access to CZone circuits. Your account may not have the required permissions.'
733
783
  : 'Could not load CZone circuits (HTTP ' + response.status + '). Check the Signal K server and CZone plugin.';
734
784
  showLoadError(message, signIn);
735
- updateSocketIndicator(signIn ? 'Sign in required' : 'Unable to load CZone circuits', false);
785
+ // The plotter hides the banner, and its login is the Navico embedder's token.
786
+ updateSocketIndicator(signIn ? (MFD ? 'Sign in required: set the token in the Navico embedder' : 'Sign in required') : 'Unable to load CZone circuits', false);
736
787
  }
737
788
 
738
789
  async function refreshOnce(reason = 'manual') {