<%- include('top') %>

<div class="container mt-4">
  <div class="row">
    <div class="col-12">
      <div class="card shadow">
        <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
          <ul class="nav nav-tabs card-header-tabs" id="vault-tabs" role="tablist">
            <li class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-secrets" type="button"><i class="fa-solid fa-lock"></i> Secrets</button></li>
            <li class="nav-item" id="vault-apps-tab" style="display:none"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-apps" type="button"><i class="fa-solid fa-key"></i> Apps</button></li>
            <li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-shared" type="button"><i class="fa-solid fa-share-nodes"></i> Shared</button></li>
          </ul>
          <span class="small text-muted"><i class="fa-solid fa-database me-1"></i>Powered by <a href="https://openbao.org" target="_blank" rel="noopener">OpenBao</a></span>
        </div>
        <div class="card-body p-0">
          <div class="tab-content">
    <!-- ── Secrets tab ─────────────────────────────────────────────────── -->
    <div class="tab-pane fade show active" id="tab-secrets">
      <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <h5 class="mb-0" id="vault-title"><i class="fas fa-lock"></i> My Secrets <small class="text-muted">(personal namespace)</small></h5>
        <div class="d-flex align-items-center gap-2">
          <a href="/docs/vault" class="text-reset" title="Vault help &amp; documentation"><i class="fa-solid fa-circle-question"></i></a>
          <button class="btn btn-primary btn-sm" onclick="showCreateModal()"><i class="fas fa-plus"></i> New Secret</button>
        </div>
      </div>
      <div class="p-3">
        <div class="row">
        <div class="col-md-4">
          <div class="card shadow-sm">
            <div class="card-header"><h5 class="card-title mb-0">Secrets List</h5></div>
            <div class="list-group list-group-flush" id="secrets-list">
              <div class="list-group-item text-center text-muted">Loading...</div>
            </div>
          </div>
        </div>
        <div class="col-md-8">
          <div class="card shadow-sm" id="secret-details-card" style="display: none;">
            <div class="card-header d-flex justify-content-between align-items-center">
              <h5 class="card-title mb-0" id="secret-title">Secret Details</h5>
              <div>
                <button class="btn btn-sm btn-outline-primary me-2" onclick="editCurrentSecret()"><i class="fas fa-edit"></i> Edit</button>
                <button class="btn btn-sm btn-outline-danger" onclick="deleteCurrentSecret()"><i class="fas fa-trash"></i> Delete</button>
              </div>
            </div>
            <div class="card-body">
              <pre id="secret-content" class="bg-dark text-light p-3 rounded" style="min-height: 200px;"></pre>
            </div>
          </div>
          <div id="no-secret-selected" class="text-center text-muted mt-5">
            <i class="fas fa-key fa-4x mb-3 text-secondary"></i>
            <h4>Select a secret to view its details</h4>
          </div>
        </div>
        </div>
      </div>
    </div>

    <!-- ── Apps tab (admin only; revealed client-side for admins) ─────── -->
    <div class="tab-pane fade" id="tab-apps">
      <div class="p-3">
        <div class="row">
        <div class="col-md-5">
          <div class="card shadow-sm">
            <div class="card-header"><h5 class="card-title mb-0">Mint an app token</h5></div>
            <div class="card-body">
              <p class="text-muted small">Mints a scoped OpenBao token confined to <code>secret/apps/&lt;name&gt;/*</code> for an external app. The token is shown <strong>once</strong> — record it in the app immediately; it cannot be recovered later.</p>
              <p class="text-muted small">The token is periodic: it stays valid as long as the app renews it within its period (<code>POST /v1/auth/token/renew-self</code>). If it lapses, mint a new one here — the app's policy and stored secrets are kept.</p>
              <div class="mb-3">
                <label class="form-label">App name (lowercase letters, digits, hyphens)</label>
                <input type="text" class="form-control" id="app-name-input" placeholder="e.g. my-service">
              </div>
              <button class="btn btn-primary" onclick="mintApp()"><i class="fas fa-key"></i> Mint token</button>
              <div class="alert alert-danger d-none mt-3" id="app-error"></div>
            </div>
          </div>
        </div>
        <div class="col-md-7">
          <div class="card shadow-sm d-none" id="app-result-card">
            <div class="card-header d-flex justify-content-between align-items-center">
              <h5 class="card-title mb-0">App token</h5>
              <button class="btn btn-sm btn-outline-primary" onclick="copyText(document.getElementById('app-token').textContent)"><i class="fas fa-copy"></i> Copy</button>
            </div>
            <div class="card-body">
              <p class="small text-muted">Give the external app this token (header <code>X-Vault-Token</code>) and the path convention below.</p>
              <pre id="app-token" class="bg-dark text-light p-3 rounded"></pre>
              <h6 class="mt-3">Connection convention</h6>
              <pre class="bg-light p-2 rounded small">VAULT_ADDR=<%- vaultAddr %>
path=secret/apps/&lt;name&gt;/conf
curl "$VAULT_ADDR/v1/secret/data/apps/<span id="app-name-display"></span>/conf" \
  -H "X-Vault-Token: &lt;token above&gt;"</pre>
            </div>
          </div>
        </div>
        </div>
        <div class="row mt-3">
          <div class="col-12">
            <div class="card shadow-sm">
              <div class="card-header d-flex justify-content-between align-items-center">
                <h5 class="card-title mb-0"><i class="fa-solid fa-key me-1"></i> Minted apps</h5>
                <button class="btn btn-sm btn-outline-primary" onclick="loadApps()"><i class="fas fa-rotate"></i> Refresh</button>
              </div>
              <div class="card-body">
                <p class="text-muted small mb-2">Each entry is a scoped OpenBao credential an external service uses to read <code>secret/apps/&lt;name&gt;/*</code>. The token itself is shown <strong>once</strong> at mint — this list is metadata sso keeps so it can renew the token and so you can see what's been minted. If an app shows a renewal error, re-mint it here.</p>
                <div id="apps-list"><div class="text-muted small">Loading…</div></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- ── Shared tab ─────────────────────────────────────────────────── -->
    <div class="tab-pane fade" id="tab-shared">
      <div class="p-3">
        <div class="row">
        <div class="col-md-6">
          <div class="card shadow-sm">
            <div class="card-header d-flex justify-content-between align-items-center">
              <h5 class="card-title mb-0">My shared secrets</h5>
              <button class="btn btn-sm btn-primary" onclick="showCreateSharedModal()"><i class="fas fa-plus"></i> New</button>
            </div>
            <div class="list-group list-group-flush" id="shared-mine-list">
              <div class="list-group-item text-center text-muted">Loading...</div>
            </div>
          </div>
        </div>
        <div class="col-md-6">
          <div class="card shadow-sm">
            <div class="card-header"><h5 class="card-title mb-0">Shared with me</h5></div>
            <div class="list-group list-group-flush" id="shared-granted-list">
              <div class="list-group-item text-center text-muted">Loading...</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Create Shared Secret Modal -->
<div class="modal fade" id="sharedCreateModal" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">New Shared Secret</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="mb-3">
          <label class="form-label">Name (slug)</label>
          <input type="text" class="form-control" id="shared-slug-input" placeholder="e.g. db-creds">
        </div>
        <div class="mb-3">
          <label class="form-label">Description</label>
          <input type="text" class="form-control" id="shared-desc-input" placeholder="optional">
        </div>
        <div class="mb-3">
          <label class="form-label">Secret Data (JSON)</label>
          <textarea class="form-control" id="shared-data-input" rows="6" style="font-family: monospace;">{
  "key": "value"
}</textarea>
        </div>
        <div class="alert alert-danger d-none" id="shared-create-error"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary" onclick="saveSharedSecret()">Create</button>
      </div>
    </div>
  </div>
</div>

<!-- Manage Grants Modal -->
<div class="modal fade" id="sharedGrantsModal" tabindex="-1">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Share</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="row g-2 mb-3">
          <div class="col-4"><select class="form-select" id="grant-type-input"><option value="user">User</option><option value="app">App</option></select></div>
          <div class="col-5"><input class="form-control" id="grant-id-input" placeholder="uid or app name"></div>
          <div class="col-3"><button class="btn btn-primary w-100" onclick="addGrant()">Grant</button></div>
        </div>
        <div class="alert alert-danger d-none" id="grants-error"></div>
        <div class="list-group" id="grants-list"><div class="list-group-item text-muted">No grants yet.</div></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

<!-- View Shared Secret Modal -->
<div class="modal fade" id="sharedViewModal" tabindex="-1">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="shared-view-title">Secret</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body"><pre id="shared-view-content" class="bg-dark text-light p-3 rounded" style="min-height: 200px;"></pre></div>
    </div>
  </div>
</div>

<!-- Create/Edit Secret Modal -->
<div class="modal fade" id="secretModal" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="secretModalTitle">Create Secret</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="mb-3">
          <label class="form-label" id="secret-path-label">Secret name (in your personal namespace)</label>
          <input type="text" class="form-control" id="secret-path-input" placeholder="e.g. database-creds">
        </div>
        <div class="mb-3">
          <label class="form-label">Secret Data (JSON)</label>
          <textarea class="form-control" id="secret-data-input" rows="8" style="font-family: monospace;">{
  "username": "",
  "password": ""
}</textarea>
        </div>
        <div class="alert alert-danger d-none" id="secret-error"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary" onclick="saveSecret()">Save Secret</button>
      </div>
    </div>
  </div>
</div>

<script>
  // Login gate + client-derived scoping. VAULT_BASE is '' for admins
  // (free-form under secret/) or 'users/<uid>/' for everyone else (confined
  // to their personal namespace). The /api/vault proxy enforces the same
  // server-side (scopeGuard + the token's OpenBao policy), so this only
  // drives the UI. Resolved in init() after forceLogin loads the user — the
  // previous version read these server-side from req.user, which is undefined
  // on a browser navigation (auth-token is a client-set header, not a cookie).
  let VAULT_BASE = '';
  let IS_ADMIN = false;

  let currentSecretPath = null;
  const secretModal = new bootstrap.Modal(document.getElementById('secretModal'));

  // Build a vault API path. kind is 'data' or 'metadata'; key is the logical
  // key relative to the subject's namespace (so 'foo' for a user means
  // secret/data/users/<uid>/foo).
  function vpath(kind, key) {
    let cleanKey = key || '';
    if (cleanKey.startsWith('/')) cleanKey = cleanKey.slice(1);
    if (VAULT_BASE) {
      return `secret/${kind}/${VAULT_BASE}${cleanKey}`;
    }
    return `secret/${kind}/${cleanKey}`;
  }

  function apiCall(method, path, body = null) {
    const opts = {
      method,
      headers: { 'Content-Type': 'application/json', 'auth-token': app.auth.getToken() }
    };
    if (body) opts.body = JSON.stringify(body);
    return fetch('/api/vault/' + path, opts).then(async res => {
      if (res.status === 404) return null;
      if (!res.ok) {
        const text = await res.text();
        throw new Error(`Vault API error: ${res.status} ${text}`);
      }
      if (res.status === 204) return null;
      return res.json();
    });
  }

  async function loadSecrets() {
    try {
      const listPath = vpath('metadata', '').replace(/\/$/, '') + '?list=true';
      const res = await apiCall('GET', listPath);
      const listEl = document.getElementById('secrets-list');
      listEl.innerHTML = '';
      if (!res || !res.data || !res.data.keys || res.data.keys.length === 0) {
        listEl.innerHTML = '<div class="list-group-item text-center text-muted">No secrets found</div>';
        return;
      }
      res.data.keys.forEach(key => {
        // KV list returns dir entries with a trailing slash; admins can still
        // open them by typing the full path in the modal. Skip dirs in the list
        // for non-admins (their namespace is flat).
        if (!IS_ADMIN && key.endsWith('/')) return;
        const item = document.createElement('a');
        item.href = '#';
        item.className = 'list-group-item list-group-item-action d-flex align-items-center';
        item.innerHTML = `<i class="fas fa-file-alt text-secondary me-3"></i> <span>${key}</span>`;
        item.onclick = (e) => {
          e.preventDefault();
          document.querySelectorAll('#secrets-list .active').forEach(el => el.classList.remove('active'));
          item.classList.add('active');
          loadSecretDetails(key);
        };
        listEl.appendChild(item);
      });
    } catch (err) {
      document.getElementById('secrets-list').innerHTML =
        `<div class="list-group-item text-danger"><i class="fas fa-exclamation-triangle"></i> Error loading secrets: ${err.message}</div>`;
    }
  }

  async function loadSecretDetails(key) {
    try {
      currentSecretPath = key;
      document.getElementById('no-secret-selected').style.display = 'none';
      document.getElementById('secret-details-card').style.display = 'block';
      document.getElementById('secret-title').textContent = key;
      document.getElementById('secret-content').textContent = 'Loading...';
      const res = await apiCall('GET', vpath('data', key));
      if (!res || !res.data || !res.data.data) {
        document.getElementById('secret-content').textContent = 'No data found.';
      } else {
        document.getElementById('secret-content').textContent = JSON.stringify(res.data.data, null, 2);
      }
    } catch (err) {
      document.getElementById('secret-content').textContent = `Error: ${err.message}`;
    }
  }

  function showCreateModal() {
    currentSecretPath = null;
    document.getElementById('secretModalTitle').textContent = 'Create Secret';
    document.getElementById('secret-path-input').value = '';
    document.getElementById('secret-path-input').disabled = false;
    document.getElementById('secret-data-input').value = '{\n  "key": "value"\n}';
    document.getElementById('secret-error').classList.add('d-none');
    secretModal.show();
  }

  function editCurrentSecret() {
    if (!currentSecretPath) return;
    document.getElementById('secretModalTitle').textContent = 'Edit Secret';
    document.getElementById('secret-path-input').value = currentSecretPath;
    document.getElementById('secret-path-input').disabled = true;
    document.getElementById('secret-data-input').value = document.getElementById('secret-content').textContent;
    document.getElementById('secret-error').classList.add('d-none');
    secretModal.show();
  }

  async function saveSecret() {
    const errorEl = document.getElementById('secret-error');
    errorEl.classList.add('d-none');
    const path = document.getElementById('secret-path-input').value.trim();
    if (!path) {
      errorEl.textContent = 'Secret path is required';
      errorEl.classList.remove('d-none');
      return;
    }
    let data;
    try {
      data = JSON.parse(document.getElementById('secret-data-input').value);
    } catch (err) {
      errorEl.textContent = 'Invalid JSON: ' + err.message;
      errorEl.classList.remove('d-none');
      return;
    }
    try {
      await apiCall('POST', vpath('data', path), { data });
      secretModal.hide();
      await loadSecrets();
      if (currentSecretPath === path || !currentSecretPath) {
        await loadSecretDetails(path);
      }
    } catch (err) {
      errorEl.textContent = err.message;
      errorEl.classList.remove('d-none');
    }
  }

  async function deleteCurrentSecret() {
    if (!currentSecretPath) return;
    try {
      await apiCall('DELETE', vpath('metadata', currentSecretPath));
      currentSecretPath = null;
      document.getElementById('no-secret-selected').style.display = 'block';
      document.getElementById('secret-details-card').style.display = 'none';
      await loadSecrets();
    } catch (err) {
      app.messages.toast('Error deleting secret: ' + err.message, 'danger');
    }
  }

  // ── Apps tab (admin) ───────────────────────────────────────────────────
  async function mintApp() {
    const errorEl = document.getElementById('app-error');
    errorEl.classList.add('d-none');
    const name = document.getElementById('app-name-input').value.trim();
    if (!name) {
      errorEl.textContent = 'App name is required';
      errorEl.classList.remove('d-none');
      return;
    }
    try {
      const res = await fetch('/api/vault/apps', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'auth-token': app.auth.getToken() },
        body: JSON.stringify({ name })
      });
      if (!res.ok) {
        const text = await res.text();
        throw new Error(`${res.status} ${text}`);
      }
      const result = await res.json();
      document.getElementById('app-token').textContent = result.token;
      document.getElementById('app-name-display').textContent = name;
      document.getElementById('app-result-card').classList.remove('d-none');
      loadApps();
    } catch (err) {
      errorEl.textContent = err.message;
      errorEl.classList.remove('d-none');
    }
  }

  // List the minted external-app tokens (metadata only). Makes the Apps tab show
  // what's been minted instead of a credential that vanishes after the once-only
  // token display.
  async function loadApps() {
    const $list = document.getElementById('apps-list');
    if (!$list) return;
    $list.textContent = 'Loading…';
    try {
      const res = await fetch('/api/vault/apps', {
        headers: { 'auth-token': app.auth.getToken() }
      });
      if (!res.ok) { $list.innerHTML = '<div class="text-danger small">Failed to load apps.</div>'; return; }
      const { apps = [] } = await res.json();
      if (!apps.length) { $list.innerHTML = '<div class="text-muted small">No apps minted yet.</div>'; return; }
      $list.innerHTML = '<div class="list-group shadow-sm">' + apps.map(a => {
        const ok = !a.lastError;
        const renewed = a.lastRenewedAt ? ' · renewed ' + moment(a.lastRenewedAt).fromNow() : ' · never renewed';
        return `<div class="list-group-item d-flex justify-content-between align-items-center">
          <div>
            <strong class="font-monospace">${app.util.escapeHtml(a.name)}</strong>
            ${ok ? '<span class="badge bg-success ms-1">renewing</span>' : '<span class="badge bg-danger ms-1" title="' + app.util.escapeHtml(a.lastError) + '">renewal error</span>'}
            <div class="small text-muted">minted ${moment(a.createdOn).format('YYYY-MM-DD HH:mm')}${renewed}</div>
          </div>
          <span class="font-monospace small text-muted">secret/apps/${app.util.escapeHtml(a.name)}/</span>
        </div>`;
      }).join('') + '</div>';
    } catch (err) {
      $list.innerHTML = '<div class="text-danger small">Failed to load apps: ' + app.util.escapeHtml(err.message) + '</div>';
    }
  }

  function copyText(text) {
    navigator.clipboard.writeText(text).then(() => app.messages.toast('Copied', 'success'));
  }

  // ── Shared secrets tab ──────────────────────────────────────────────
  let currentShared = null;
  const sharedCreateModal = new bootstrap.Modal(document.getElementById('sharedCreateModal'));
  const sharedGrantsModal = new bootstrap.Modal(document.getElementById('sharedGrantsModal'));
  const sharedViewModal = new bootstrap.Modal(document.getElementById('sharedViewModal'));

  function sharedApi(path, method = 'GET', body = null) {
    const opts = { method, headers: { 'Content-Type': 'application/json', 'auth-token': app.auth.getToken() } };
    if (body) opts.body = JSON.stringify(body);
    return fetch('/api/shared-secrets' + path, opts).then(async res => {
      if (res.status === 404) return null;
      if (!res.ok) { const t = await res.text(); throw new Error(`${res.status} ${t}`); }
      if (res.status === 204) return null;
      return res.json();
    });
  }

  async function loadShared() {
    try {
      const res = await sharedApi('/');
      const items = (res && res.items) || [];
      renderSharedMine(items.filter(i => i.role === 'owner'));
      renderSharedGranted(items.filter(i => i.role === 'grantee'));
    } catch (err) {
      document.getElementById('shared-mine-list').innerHTML =
        `<div class="list-group-item text-danger">Error: ${err.message}</div>`;
    }
  }

  function renderSharedMine(items) {
    const el = document.getElementById('shared-mine-list');
    if (!items.length) { el.innerHTML = '<div class="list-group-item text-center text-muted">No shared secrets yet</div>'; return; }
    el.innerHTML = '';
    items.forEach(s => {
      const row = document.createElement('div');
      row.className = 'list-group-item d-flex justify-content-between align-items-center';
      row.innerHTML = `<div><i class="fas fa-share-alt text-secondary me-2"></i><strong>${s.slug}</strong><div class="small text-muted">${s.path}</div></div>
        <div class="btn-group">
          <button class="btn btn-sm btn-outline-primary" onclick="openGrants('${s.id}')"><i class="fas fa-users"></i> Share</button>
          <button class="btn btn-sm btn-outline-danger" onclick="deleteShared('${s.id}')"><i class="fas fa-trash"></i></button>
        </div>`;
      el.appendChild(row);
    });
  }

  function renderSharedGranted(items) {
    const el = document.getElementById('shared-granted-list');
    if (!items.length) { el.innerHTML = '<div class="list-group-item text-center text-muted">Nothing shared with you yet</div>'; return; }
    el.innerHTML = '';
    items.forEach(s => {
      const row = document.createElement('a');
      row.href = '#';
      row.className = 'list-group-item list-group-item-action d-flex align-items-center';
      row.innerHTML = `<i class="fas fa-key text-secondary me-3"></i><span>${s.slug}</span><small class="text-muted ms-auto">by ${s.ownerUid}</small>`;
      row.onclick = (e) => { e.preventDefault(); viewShared(s); };
      el.appendChild(row);
    });
  }

  function showCreateSharedModal() {
    currentShared = null;
    document.getElementById('shared-slug-input').value = '';
    document.getElementById('shared-desc-input').value = '';
    document.getElementById('shared-data-input').value = '{\n  "key": "value"\n}';
    document.getElementById('shared-create-error').classList.add('d-none');
    sharedCreateModal.show();
  }

  async function saveSharedSecret() {
    const err = document.getElementById('shared-create-error');
    err.classList.add('d-none');
    let data;
    try { data = JSON.parse(document.getElementById('shared-data-input').value); }
    catch (e) { err.textContent = 'Invalid JSON: ' + e.message; err.classList.remove('d-none'); return; }
    try {
      await sharedApi('/', 'POST', {
        slug: document.getElementById('shared-slug-input').value.trim(),
        description: document.getElementById('shared-desc-input').value.trim(),
        data
      });
      sharedCreateModal.hide();
      await loadShared();
    } catch (e) { err.textContent = e.message; err.classList.remove('d-none'); }
  }

  async function viewShared(s) {
    document.getElementById('shared-view-title').textContent = s.slug + ' (by ' + s.ownerUid + ')';
    document.getElementById('shared-view-content').textContent = 'Loading...';
    sharedViewModal.show();
    try {
      const res = await apiCall('GET', 'secret/data/' + s.path);
      document.getElementById('shared-view-content').textContent =
        (res && res.data && res.data.data) ? JSON.stringify(res.data.data, null, 2) : 'No data found.';
    } catch (e) {
      document.getElementById('shared-view-content').textContent = 'Error: ' + e.message;
    }
  }

  async function openGrants(id) {
    currentShared = id;
    document.getElementById('grants-error').classList.add('d-none');
    document.getElementById('grant-id-input').value = '';
    sharedGrantsModal.show();
    try {
      const res = await sharedApi('/' + id + '/grants');
      const grants = (res && res.grants) || [];
      const el = document.getElementById('grants-list');
      el.innerHTML = '';
      if (!grants.length) el.innerHTML = '<div class="list-group-item text-muted">No grants yet.</div>';
      grants.forEach(g => {
        const row = document.createElement('div');
        row.className = 'list-group-item d-flex justify-content-between align-items-center';
        row.innerHTML = `<span><span class="badge bg-secondary me-2">${g.granteeType}</span>${g.granteeId}</span>
          <button class="btn btn-sm btn-outline-danger" onclick="revokeGrant('${g.id}')"><i class="fas fa-times"></i></button>`;
        el.appendChild(row);
      });
    } catch (e) {
      document.getElementById('grants-list').innerHTML = `<div class="list-group-item text-danger">${e.message}</div>`;
    }
  }

  async function addGrant() {
    const err = document.getElementById('grants-error');
    err.classList.add('d-none');
    try {
      await sharedApi('/' + currentShared + '/grants', 'POST', {
        granteeType: document.getElementById('grant-type-input').value,
        granteeId: document.getElementById('grant-id-input').value.trim()
      });
      document.getElementById('grant-id-input').value = '';
      openGrants(currentShared);
    } catch (e) { err.textContent = e.message; err.classList.remove('d-none'); }
  }

  async function revokeGrant(grantId) {
    try { await sharedApi('/' + currentShared + '/grants/' + grantId, 'DELETE'); openGrants(currentShared); }
    catch (e) { app.messages.toast('Error revoking: ' + e.message, 'danger'); }
  }

  async function deleteShared(id) {
    const confirmed = await app.messages.confirm('Delete this shared secret? Grantees will immediately lose access.', $('#shared-mine-list'), 'warning');
    if (!confirmed) return;
    try { await sharedApi('/' + id, 'DELETE'); await loadShared(); }
    catch (e) { app.messages.toast('Error deleting: ' + e.message, 'danger'); }
  }

  (async function init() {
    const user = await app.auth.forceLogin();
    if (!user) return; // not logged in — forceLogin redirected to /login
    IS_ADMIN = app.auth.isAdmin();
    VAULT_BASE = IS_ADMIN ? '' : 'users/' + user.uid + '/';
    if (IS_ADMIN) {
      document.getElementById('vault-apps-tab').style.display = '';
      document.getElementById('vault-title').innerHTML =
        '<i class="fas fa-lock"></i> Vault Secrets <small class="text-muted">(admin — all of secret/)</small>';
      document.getElementById('secret-path-label').textContent = 'Secret path (under secret/)';
      document.getElementById('secret-path-input').placeholder = 'e.g. apps/my-service/conf';
      loadApps();
    }
    loadSecrets();
    loadShared();
  })();
</script>

<%- include('bottom') %>