aipackage/cezen-portal/change-password.html
Jino Jose b3eec34158 Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)
Brings the ISO-packaged portal copy current with the live cezen-portal/
working directory, which had drifted across the entire V1.0 GA polish
initiative (Phases 1-10) plus this session's topnav icon-order bugfix.

- 52 existing files updated, 2 new pages added (about.html, health.html)
- cgit/cezen-portal/ is now byte-identical to cezen-portal/
- Backend files (rag_ingest.py, cezen_license.py) already in sync;
  main.py intentionally left as-is (cgit is ahead there with
  workstation-category commercial-fit logic not yet backported to the
  live working copy)

Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
2026-07-09 11:11:38 +05:30

118 lines
5.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Change Password — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=12">
<style>
body { background:var(--navy); min-height:100vh; display:flex; align-items:center; justify-content:center; padding:24px; }
.cp-card { background:var(--navy2); border-radius:20px; padding:40px 44px; width:100%; max-width:420px; box-shadow:0 24px 64px rgba(0,0,0,.35); }
.cp-brand { font-size:22px; font-weight:800; color:var(--navy); margin-bottom:6px; }
.cp-brand span { color:var(--teal); }
.cp-headline { font-size:18px; font-weight:700; color:var(--ink); margin-bottom:6px; }
.cp-sub { font-size:13px; color:var(--lt); margin-bottom:28px; line-height:1.5; }
.cp-field { margin-bottom:16px; }
.cp-label { display:block; font-size:13px; font-weight:600; color:var(--med); margin-bottom:5px; }
.cp-input { width:100%; padding:11px 13px; border:1.5px solid var(--bdr); border-radius:9px; font-size:14px; font-family:inherit; color:var(--ink); outline:none; box-sizing:border-box; }
.cp-input:focus { border-color:var(--teal); }
.cp-err { background:rgba(185,28,28,.08); border:1px solid rgba(239,68,68,.25); color:#B91C1C; padding:9px 12px; border-radius:7px; font-size:12px; margin-bottom:14px; display:none; }
.cp-err.show { display:block; }
.cp-ok { background:rgba(34,197,94,.08); border:1px solid #86EFAC; color:#15803D; padding:9px 12px; border-radius:7px; font-size:12px; margin-bottom:14px; display:none; }
.cp-ok.show { display:block; }
.cp-btn { width:100%; padding:12px; background:var(--teal); color:var(--ink); border:none; border-radius:9px; font-size:15px; font-weight:700; cursor:pointer; font-family:inherit; margin-top:6px; transition:background .15s; }
.cp-btn:hover { background:#0B7A70; }
.cp-btn:disabled { opacity:.6; cursor:not-allowed; }
.cp-rules { font-size:11px; color:var(--lt); margin-top:10px; line-height:1.8; }
</style>
</head>
<body>
<div class="cp-card">
<div class="cp-brand">Nexus One <span>AI</span></div>
<div class="cp-headline">Set New Password</div>
<div class="cp-sub">Your password needs to be changed before you can continue. Choose a strong password you haven't used before.</div>
<div class="cp-err" id="cp-err"></div>
<div class="cp-ok" id="cp-ok">Password changed! Taking you to Quick Start…</div>
<div class="cp-field">
<label class="cp-label">New Password</label>
<input class="cp-input" id="new-pw" type="password" placeholder="New password" autocomplete="new-password">
</div>
<div class="cp-field">
<label class="cp-label">Confirm Password</label>
<input class="cp-input" id="conf-pw" type="password" placeholder="Confirm new password" autocomplete="new-password">
</div>
<button class="cp-btn" id="cp-btn" onclick="doChange()">Change Password</button>
<div class="cp-rules">
At least 8 characters &nbsp; and &nbsp; Mix letters &amp; numbers
</div>
</div>
<script>
const API = '/api';
document.querySelectorAll('.cp-input').forEach(el => {
el.addEventListener('keydown', e => { if (e.key === 'Enter') doChange(); });
});
async function doChange() {
const errEl = document.getElementById('cp-err');
const okEl = document.getElementById('cp-ok');
const btn = document.getElementById('cp-btn');
errEl.classList.remove('show');
const np = document.getElementById('new-pw').value;
const cp = document.getElementById('conf-pw').value;
if (!np) { errEl.textContent = 'Enter a new password.'; errEl.classList.add('show'); return; }
if (np.length < 8) { errEl.textContent = 'Password must be at least 8 characters.'; errEl.classList.add('show'); return; }
if (np !== cp) { errEl.textContent = 'Passwords do not match.'; errEl.classList.add('show'); return; }
btn.disabled = true;
btn.textContent = 'Changing…';
try {
const res = await fetch(`${API}/auth/change-password`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ new_password: np })
});
if (res.ok) {
okEl.classList.add('show');
// Forced password change only happens on an account's very first login
// (default admin, or any account an admin just created) — so this is
// the real "first run" moment. Send them to the guided Quick Start
// instead of straight to the dashboard.
setTimeout(() => { window.location.href = '/quickstart.html'; }, 1500);
} else {
const d = await res.json().catch(() => ({}));
const msg = Array.isArray(d.detail)
? d.detail.map(e => e.msg || JSON.stringify(e)).join(', ')
: (typeof d.detail === 'string' ? d.detail : 'Failed to change password.');
errEl.textContent = msg;
errEl.classList.add('show');
btn.disabled = false;
btn.textContent = 'Change Password';
}
} catch {
errEl.textContent = 'Network error. Try again.';
errEl.classList.add('show');
btn.disabled = false;
btn.textContent = 'Change Password';
}
}
// Verify user is logged in (don't redirect to login for change-password page)
fetch(`${API}/auth/me`, { credentials: 'include' }).then(r => {
if (!r.ok) window.location.href = '/login.html';
});
</script>
<script src="branding.js?v=3"></script>
</body>
</html>