code wiki / _hdl_build / nx_hr_admin_page.nx

nx_hr_admin_page.nx source

↩ module page · 98 lines · 6761 B

1// nx_hr_admin_page.nx -- the sovereign /hr ADMIN CONSOLE page (server-rendered, ZERO third-party JS). It renders 2// the live roster from the HR store -- the "logins you see" the operator asked for -- and the invite/suspend 3// controls, so Nishi HR does the admin work through a real surface. OWNER-GATED: the daemon serves this ONLY to an 4// active superadmin (it passes viewer_is_super, computed from the caller's session uid == HR cred_id); a non-owner 5// gets an "owner-only" stub with NO roster (leak-check). The owner row shows no suspend control (no self-lockout, 6// by construction). Forms POST to /hr/invite and /hr/suspend (the daemon's owner-gated handlers). 7// roster row source: distinct cred_ids in the append-only store, LATEST record wins (handle/level/status). 8import "nx_hr.nx" // hr_puts / hr_putn (storage-agnostic string helpers) 9import "nx_hr_sov.nx" // CUTOVER: roster read from the seg_store (hrs_index_count / hrs_index_get_cred / hrs_get / hrs_field) 10import "nx_syscalls.nx" 11 12func hap_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 13 14const HAP_MAX: i64 = 256 15const HAP_BUF: i64 = 1048576 16 17func hap_eq64(a: *u8, b: *u8) -> i64 { var i: i64=0; while i<64 { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 18func hap_cpy(dst: *u8, src: *u8, n: i64) -> i64 { var i: i64=0; while i<n { dst[i]=src[i]; i=i+1 } return 0 } 19func hap_putrange(out: *u8, o: i64, src: *u8, s: i64, e: i64) -> i64 { var oo: i64=o; var i: i64=s; while i<e { out[oo]=src[i]; oo=oo+1; i=i+1 } return oo } 20func hap_role(out: *u8, o: i64, lvl: i64) -> i64 { 21 if lvl >= 3 { return hr_puts(out, o, "owner (super-admin)" as *u8) } 22 if lvl == 1 { return hr_puts(out, o, "member" as *u8) } 23 return hr_puts(out, o, "none" as *u8) 24} 25 26const HAP_HEAD: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi HR</title><style>body{font-family:system-ui,sans-serif;max-width:680px;margin:5vh auto;padding:0 18px;color:#16202e}h1{font-size:1.25rem}table{border-collapse:collapse;width:100%;margin:12px 0}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #e1e6f0}th{color:#667;font-size:.85rem}.b{font-weight:600}.s-active{color:#137333}.s-invited{color:#a56300}.s-suspended{color:#a50e0e}form{display:inline;margin:0}input,select{padding:7px;border:1px solid #b9c2d6;border-radius:5px}button{padding:7px 12px;background:#0b2545;color:#fff;border:0;border-radius:5px;cursor:pointer}.sus{background:#a50e0e;padding:4px 9px;font-size:.85rem}</style></head><body>" as *u8 27 28// emit the full /hr console into `out`; returns length. viewer_is_super gates the whole roster. 29func hap_emit(hr_store: *u8, viewer_is_super: i64, out: *u8, cap: i64) -> i64 { 30 var o: i64 = 0 31 o = hr_puts(out, o, HAP_HEAD) 32 if viewer_is_super == 0 { 33 o = hr_puts(out, o, "<h1>Nishi HR</h1><p>This console is owner-only. Your account is not a super-admin.</p></body></html>" as *u8) 34 return o 35 } 36 o = hr_puts(out, o, "<h1>Nishi HR &mdash; family roster</h1><p style=\"color:#667;font-size:.9rem\">The logins on this realm. Members sign up on the home network using the handle you invited.</p>" as *u8) 37 o = hr_puts(out, o, "<table><tr><th>handle</th><th>role</th><th>status</th><th></th></tr>" as *u8) 38 39 // collect distinct cred_ids + LATEST (handle, level, status) from the SEG_STORE via the roster index (cutover) 40 let hbuf: *u8 = sys_mmap(HAP_MAX*64) 41 let hlen: *i64 = sys_mmap(HAP_MAX*8) as *i64 42 let lvls: *i64 = sys_mmap(HAP_MAX*8) as *i64 43 let sbuf: *u8 = sys_mmap(HAP_MAX*16) 44 let slen: *i64 = sys_mmap(HAP_MAX*8) as *i64 45 var ndist: i64 = 0 46 let ncreds: i64 = hrs_index_count(hr_store) 47 let credbuf: *u8 = sys_mmap(72) 48 let ok: *i64 = sys_mmap(64*8) as *i64; let ov: *i64 = sys_mmap(64*8) as *i64 49 var ci: i64 = 0 50 while ci < ncreds { 51 if hrs_index_get_cred(hr_store, ci, credbuf) == 1 { if ndist < HAP_MAX { 52 let nf: i64 = hrs_get(hr_store, credbuf, ok, ov, 64) 53 if nf > 0 { 54 let hf: *u8 = hrs_field(ok, ov, nf, "handle" as *u8) 55 let lf: *u8 = hrs_field(ok, ov, nf, "level" as *u8) 56 let sf: *u8 = hrs_field(ok, ov, nf, "status" as *u8) 57 var hl: i64 = 0; if (hf as i64)!=0 { while hf[hl]!=(0 as u8){hl=hl+1} } if hl>63 {hl=63} 58 if (hf as i64)!=0 { hap_cpy((hbuf as i64 + ndist*64) as *u8, hf, hl) } hlen[ndist]=hl 59 if (lf as i64)!=0 { lvls[ndist]=hap_atoi(lf) } else { lvls[ndist]=0 } 60 var sl: i64 = 0; if (sf as i64)!=0 { while sf[sl]!=(0 as u8){sl=sl+1} } if sl>15 {sl=15} 61 if (sf as i64)!=0 { hap_cpy((sbuf as i64 + ndist*16) as *u8, sf, sl) } slen[ndist]=sl 62 ndist=ndist+1 63 } 64 } } 65 ci = ci + 1 66 } 67 68 // emit a row per distinct cred 69 var d: i64 = 0 70 while d < ndist { 71 let hp: *u8 = (hbuf as i64 + d*64) as *u8 72 let sp: *u8 = (sbuf as i64 + d*16) as *u8 73 o = hr_puts(out, o, "<tr><td class=b>" as *u8) 74 o = hap_putrange(out, o, hp, 0, hlen[d]) 75 o = hr_puts(out, o, "</td><td>" as *u8) 76 o = hap_role(out, o, lvls[d]) 77 o = hr_puts(out, o, "</td><td class=\"s-" as *u8) 78 o = hap_putrange(out, o, sp, 0, slen[d]) 79 o = hr_puts(out, o, "\">" as *u8) 80 o = hap_putrange(out, o, sp, 0, slen[d]) 81 o = hr_puts(out, o, "</td><td>" as *u8) 82 // owner gets NO suspend control (no self-lockout, by construction) 83 if lvls[d] >= 3 { o = hr_puts(out, o, "&mdash;" as *u8) } 84 else { 85 o = hr_puts(out, o, "<form method=post action=/hr/suspend><input type=hidden name=handle value=\"" as *u8) 86 o = hap_putrange(out, o, hp, 0, hlen[d]) 87 o = hr_puts(out, o, "\"><button class=sus>suspend</button></form>" as *u8) 88 } 89 o = hr_puts(out, o, "</td></tr>" as *u8) 90 d = d + 1 91 } 92 o = hr_puts(out, o, "</table>" as *u8) 93 94 // invite control (POSTs to the daemon's owner-gated handler) 95 o = hr_puts(out, o, "<h3>Invite a family member</h3><form method=post action=/hr/invite><input name=handle placeholder=\"handle (their first name)\"> <select name=level><option value=1>member</option><option value=3>owner</option></select> <button>Invite</button></form>" as *u8) 96 o = hr_puts(out, o, "<p style=\"color:#667;font-size:.85rem;margin-top:16px\">Invited members sign up on the home network &mdash; they choose their own passphrase (the server never sees it).</p><p style=\"margin-top:8px\"><a href=/hr/audit>View the audit trail &rarr;</a></p></body></html>" as *u8) 97 return o 98}