Add staff visibility workstation enrollment registration vault and category task views
This commit is contained in:
@@ -1 +1,74 @@
|
||||
{% extends "base/layout.html" %}{% block content %}<div class="mx-auto max-w-4xl p-4 sm:p-6"><h1 class="mb-5 text-2xl font-bold">Add encrypted credential</h1><form method="post" class="grid gap-4 rounded-xl bg-white p-6 shadow sm:grid-cols-2"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><label class="sm:col-span-2">Title<input required name="title" class="mt-1 w-full rounded-lg border p-2"></label><label>Category<select name="category" class="mt-1 w-full rounded-lg border p-2"><option value="government_portal">Government portal</option><option value="banking">Banking</option><option value="email">Email</option><option value="software">Software</option><option value="api_key">API key</option><option value="digital_signature">Digital signature</option><option value="other">Other</option></select></label><label>Sensitivity<select name="sensitivity" class="mt-1 w-full rounded-lg border p-2"><option>high</option><option>critical</option><option>standard</option></select></label><label>Client<select name="client_id" class="mt-1 w-full rounded-lg border p-2"><option value="">Firm-level credential</option>{% for c in clients %}<option value="{{ c.id }}">{{ c.client_name }}</option>{% endfor %}</select></label><label>Registration record<select name="registration_id" class="mt-1 w-full rounded-lg border p-2"><option value="">Not linked</option>{% for r in registrations %}<option value="{{ r.id }}">{{ r.registration_number }}</option>{% endfor %}</select></label><label class="sm:col-span-2">Portal URL<input name="portal_url" type="url" class="mt-1 w-full rounded-lg border p-2"></label><label>Reference number<input name="reference_number" class="mt-1 w-full rounded-lg border p-2"></label><label>Username/login ID<input name="username" autocomplete="off" class="mt-1 w-full rounded-lg border p-2"></label><label>Secret/password/token<input required name="secret" type="password" autocomplete="new-password" class="mt-1 w-full rounded-lg border p-2"></label><label>Additional secret/PIN<input name="additional_secret" type="password" autocomplete="new-password" class="mt-1 w-full rounded-lg border p-2"></label><label>Expires on<input name="expires_on" type="date" class="mt-1 w-full rounded-lg border p-2"></label><label>Rotation due on<input name="rotation_due_on" type="date" class="mt-1 w-full rounded-lg border p-2"></label><label>Owner<select name="owner_user_id" class="mt-1 w-full rounded-lg border p-2"><option value="">Current user</option>{% for u in users %}<option value="{{ u.id }}">{{ u.full_name or u.email }}</option>{% endfor %}</select></label><fieldset><legend>Explicit staff access</legend><div class="mt-1 max-h-32 overflow-auto rounded-lg border p-2">{% for u in users %}<label class="block text-sm"><input type="checkbox" name="allowed_user_ids" value="{{ u.id }}"> {{ u.full_name or u.email }}</label>{% endfor %}</div></fieldset><label class="sm:col-span-2">Encrypted notes<textarea name="notes" rows="3" class="mt-1 w-full rounded-lg border p-2"></textarea></label><div class="sm:col-span-2 flex gap-2"><button class="rounded-lg bg-brand-600 px-4 py-2 font-semibold text-white">Save securely</button><a href="/credential-vault" class="rounded-lg border px-4 py-2">Cancel</a></div></form></div>{% endblock %}
|
||||
{% extends "base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-4xl p-4 sm:p-6">
|
||||
<div class="mb-5">
|
||||
<h1 class="text-2xl font-bold">Add encrypted credential</h1>
|
||||
{% if selected_registration %}
|
||||
<p class="mt-1 text-sm text-slate-600">Linked to registration <strong>{{ selected_registration.registration_number }}</strong>. Secrets remain encrypted and reveal access is audited.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" class="grid gap-4 rounded-xl bg-white p-6 shadow sm:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<label class="sm:col-span-2">Title<input required name="title" class="mt-1 w-full rounded-lg border p-2" placeholder="e.g. GST Portal - Main Login"></label>
|
||||
<label>Credential type
|
||||
<select name="category" class="mt-1 w-full rounded-lg border p-2">
|
||||
<option value="gst_portal">GST Portal</option>
|
||||
<option value="income_tax_portal">Income Tax Portal</option>
|
||||
<option value="traces_tds">TRACES / TDS</option>
|
||||
<option value="mca_portal">MCA Portal</option>
|
||||
<option value="eway_bill">E-Way Bill</option>
|
||||
<option value="einvoice">E-Invoice</option>
|
||||
<option value="government_portal">Other Government Portal</option>
|
||||
<option value="banking">Banking</option>
|
||||
<option value="email">Email</option>
|
||||
<option value="software">Software</option>
|
||||
<option value="api_key">API key</option>
|
||||
<option value="digital_signature">Digital signature</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Sensitivity<select name="sensitivity" class="mt-1 w-full rounded-lg border p-2"><option>high</option><option>critical</option><option>standard</option></select></label>
|
||||
<label>Client
|
||||
<select name="client_id" id="vault-client" class="mt-1 w-full rounded-lg border p-2">
|
||||
<option value="">Firm-level credential</option>
|
||||
{% for c in clients %}<option value="{{ c.id }}" {% if selected_client_id and c.id == selected_client_id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Registration record
|
||||
<select name="registration_id" id="vault-registration" class="mt-1 w-full rounded-lg border p-2">
|
||||
<option value="">Not linked</option>
|
||||
{% for r in registrations %}<option value="{{ r.id }}" data-client-id="{{ r.client_id }}" {% if selected_registration_id and r.id == selected_registration_id %}selected{% endif %}>{{ r.registration_number }}</option>{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="sm:col-span-2">Portal URL<input name="portal_url" type="url" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Reference number<input name="reference_number" class="mt-1 w-full rounded-lg border p-2" placeholder="GSTIN / TAN / CIN / login reference"></label>
|
||||
<label>Username/login ID<input name="username" autocomplete="off" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Secret/password/token<input required name="secret" type="password" autocomplete="new-password" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Additional secret/PIN<input name="additional_secret" type="password" autocomplete="new-password" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Expires on<input name="expires_on" type="date" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Rotation due on<input name="rotation_due_on" type="date" class="mt-1 w-full rounded-lg border p-2"></label>
|
||||
<label>Owner<select name="owner_user_id" class="mt-1 w-full rounded-lg border p-2"><option value="">Current user</option>{% for u in users %}<option value="{{ u.id }}">{{ u.full_name or u.email }}</option>{% endfor %}</select></label>
|
||||
<fieldset><legend>Explicit staff access</legend><div class="mt-1 max-h-32 overflow-auto rounded-lg border p-2">{% for u in users %}<label class="block text-sm"><input type="checkbox" name="allowed_user_ids" value="{{ u.id }}"> {{ u.full_name or u.email }}</label>{% endfor %}</div></fieldset>
|
||||
<label class="sm:col-span-2">Encrypted notes<textarea name="notes" rows="3" class="mt-1 w-full rounded-lg border p-2"></textarea></label>
|
||||
<div class="sm:col-span-2 flex gap-2"><button class="rounded-lg bg-brand-600 px-4 py-2 font-semibold text-white">Save securely</button><a href="/credential-vault" class="rounded-lg border px-4 py-2">Cancel</a></div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const client = document.getElementById('vault-client');
|
||||
const registration = document.getElementById('vault-registration');
|
||||
if (!client || !registration) return;
|
||||
const filterRegistrations = () => {
|
||||
const cid = client.value;
|
||||
Array.from(registration.options).forEach((opt, idx) => {
|
||||
if (idx === 0) { opt.hidden = false; return; }
|
||||
opt.hidden = !!cid && opt.dataset.clientId !== cid;
|
||||
});
|
||||
const selected = registration.selectedOptions[0];
|
||||
if (selected && selected.hidden) registration.value = '';
|
||||
};
|
||||
client.addEventListener('change', filterRegistrations);
|
||||
filterRegistrations();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -52,7 +52,7 @@ def dashboard(request: Request, include_archived: bool = False):
|
||||
|
||||
|
||||
@router.get("/new", response_class=HTMLResponse)
|
||||
def new_entry(request: Request):
|
||||
def new_entry(request: Request, client_id: int | None = None, registration_id: int | None = None):
|
||||
with CommonSessionLocal() as db:
|
||||
user = _user(request, db)
|
||||
if not can_manage_vault(db, user): raise HTTPException(403, "Only firm managers may create credentials.")
|
||||
@@ -60,7 +60,21 @@ def new_entry(request: Request):
|
||||
clients = db.execute(select(Client).where(Client.tenant_id == tenant_id, Client.is_active.is_(True)).order_by(Client.client_name)).scalars().all()
|
||||
users = db.execute(select(User).where(User.tenant_id == tenant_id, User.is_active.is_(True)).order_by(User.full_name)).scalars().all()
|
||||
registrations = db.execute(select(ClientRegistration).where(ClientRegistration.tenant_id == tenant_id).order_by(ClientRegistration.registration_number)).scalars().all()
|
||||
return templates.TemplateResponse("modules/credential_vault/templates/credential_vault/form.html", _ctx(request, user, db, entry=None, clients=clients, users=users, registrations=registrations, branch_id=branch_id))
|
||||
selected_registration = None
|
||||
if registration_id:
|
||||
selected_registration = db.get(ClientRegistration, int(registration_id))
|
||||
if not selected_registration or selected_registration.tenant_id != tenant_id:
|
||||
raise HTTPException(404, "Registration record was not found in this audit firm.")
|
||||
client_id = int(selected_registration.client_id)
|
||||
if client_id and not any(int(c.id) == int(client_id) for c in clients):
|
||||
raise HTTPException(404, "Client was not found in this audit firm.")
|
||||
return templates.TemplateResponse(
|
||||
"modules/credential_vault/templates/credential_vault/form.html",
|
||||
_ctx(
|
||||
request, user, db, entry=None, clients=clients, users=users, registrations=registrations, branch_id=branch_id,
|
||||
selected_client_id=client_id, selected_registration_id=registration_id, selected_registration=selected_registration,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/new")
|
||||
@@ -69,7 +83,16 @@ async def save_new(request: Request, title: str=Form(...), category: str=Form("g
|
||||
user = _user(request, db); validate_csrf(request, csrf_token)
|
||||
if not can_manage_vault(db, user): raise HTTPException(403)
|
||||
tenant_id = active_tenant_id(request, user)
|
||||
entry = create_entry(db, tenant_id=tenant_id, branch_id=active_branch_id(request, user), client_id=int(client_id) if client_id else None, registration_id=int(registration_id) if registration_id else None, title=title, category=category, portal_url=portal_url, reference_number=reference_number, username=username, secret=secret, additional_secret=additional_secret, notes=notes, sensitivity=sensitivity, expires_on=_date(expires_on), rotation_due_on=_date(rotation_due_on), owner_user_id=int(owner_user_id) if owner_user_id else user.id, allowed_user_ids_csv=",".join(allowed_user_ids), actor_user_id=user.id)
|
||||
selected_client_id = int(client_id) if client_id else None
|
||||
selected_registration_id = int(registration_id) if registration_id else None
|
||||
if selected_registration_id:
|
||||
registration = db.get(ClientRegistration, selected_registration_id)
|
||||
if not registration or registration.tenant_id != tenant_id:
|
||||
raise HTTPException(400, "Selected registration is not available in this audit firm.")
|
||||
if selected_client_id and int(registration.client_id) != selected_client_id:
|
||||
raise HTTPException(400, "Selected registration does not belong to the selected client.")
|
||||
selected_client_id = int(registration.client_id)
|
||||
entry = create_entry(db, tenant_id=tenant_id, branch_id=active_branch_id(request, user), client_id=selected_client_id, registration_id=selected_registration_id, title=title, category=category, portal_url=portal_url, reference_number=reference_number, username=username, secret=secret, additional_secret=additional_secret, notes=notes, sensitivity=sensitivity, expires_on=_date(expires_on), rotation_due_on=_date(rotation_due_on), owner_user_id=int(owner_user_id) if owner_user_id else user.id, allowed_user_ids_csv=",".join(allowed_user_ids), actor_user_id=user.id)
|
||||
log_access(db, request, user, entry, "create", reason="Credential created"); db.commit()
|
||||
return RedirectResponse(f"/credential-vault/{entry.id}", 303)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user