package ins.frontend; import ins.frontend.utils.RegistrarDao; import ins.frontend.utils.WebApp; import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket; import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags; import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader; import org.openautonomousconnection.webserver.api.Route; import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.utils.HeaderMaps; import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.MergedRequestParams; import org.openautonomousconnection.webserver.utils.WebUrlUtil; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * INS registrar dashboard (TLN / InfoName / Records) for protocol v1.0.1 resource packets. * *

Supported actions (POST recommended for mutations):

* */ @Route(path = "/dashboard.html") public final class dashboard extends WebPage { private static Integer normalizeNullableInt(String s) { if (s == null) return null; String t = s.trim(); if (t.isEmpty()) return null; try { return Integer.parseInt(t); } catch (Exception ignored) { return null; } } @Override public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception { WebApp.init(); if (ctx.session == null || !ctx.session.isValid() || ctx.session.getUser() == null) { return plain(ctx, 401, "Authentication required (session)."); } int userId; try { userId = Integer.parseInt(ctx.session.getUser()); } catch (Exception e) { return plain(ctx, 401, "Invalid session user."); } RegistrarDao dao = WebApp.get().dao(); // Build target for param merging: "/path?query" String rawTarget = WebUrlUtil.extractPathAndQuery(ctx.request.getUrl()); if (rawTarget == null) rawTarget = "/dashboard.html"; Map headers = ctx.request.getHeaders(); byte[] body = ctx.request.getBody(); MergedRequestParams p = MergedRequestParams.from(rawTarget, headers, body); String msg = null; String err = null; String action = p.getOr("action", "").trim(); if (!action.isBlank()) { try { ActionResult r = executeAction(action, p, userId, dao); msg = r.msg(); err = r.err(); } catch (Exception e) { err = "Action failed: " + safeMsg(e); } } return render(ctx, userId, msg, err, dao); } private ActionResult executeAction(String action, MergedRequestParams p, int userId, RegistrarDao dao) throws Exception { String a = action.trim().toLowerCase(Locale.ROOT); if ("create_tln".equals(a)) { String name = p.get("name"); String info = p.getOr("info", ""); boolean isPublic = p.getBool("is_public"); boolean allowSubs = p.getBool("allow_subdomains"); if (name == null || name.isBlank()) return ActionResult.err("Missing TLN name."); int id = dao.createTln(name.trim(), info, userId, isPublic, allowSubs); return ActionResult.ok("Created TLN id=" + id + " (" + name.trim() + ")"); } if ("update_tln".equals(a)) { int id = p.getInt("id", -1); String info = p.getOr("info", ""); boolean isPublic = p.getBool("is_public"); boolean allowSubs = p.getBool("allow_subdomains"); if (id <= 0) return ActionResult.err("Invalid TLN id."); boolean ok = dao.updateTlnOwned(id, userId, info, isPublic, allowSubs); return ActionResult.ok(ok ? ("Updated TLN id=" + id) : "Not owner / not found."); } if ("delete_tln".equals(a)) { int id = p.getInt("id", -1); if (id <= 0) return ActionResult.err("Invalid TLN id."); boolean ok = dao.deleteTlnOwned(id, userId); return ActionResult.ok(ok ? ("Deleted TLN id=" + id) : "Not owner / not found."); } if ("create_infoname".equals(a)) { String tlnName = p.get("tln"); String info = p.get("info"); if (tlnName == null || tlnName.isBlank() || info == null || info.isBlank()) { return ActionResult.err("Missing tln / info."); } RegistrarDao.TlnRow tln = dao.findTlnByName(tlnName.trim()).orElse(null); if (tln == null) return ActionResult.err("Unknown TLN: " + tlnName); if (!RegistrarDao.canUseTln(tln, userId)) { return ActionResult.err("TLN not public and not owned by you."); } int id = dao.createInfoName(tln, info.trim(), userId); return ActionResult.ok("Created infoname id=" + id + " (" + info.trim() + "." + tln.name() + ")"); } if ("delete_infoname".equals(a)) { int id = p.getInt("id", -1); if (id <= 0) return ActionResult.err("Invalid infoname id."); boolean ok = dao.deleteInfoNameOwned(id, userId); return ActionResult.ok(ok ? ("Deleted infoname id=" + id) : "Not owner / not found."); } if ("add_record".equals(a)) { int infonameId = p.getInt("infoname_id", -1); String sub = p.get("sub"); String type = p.getOr("type", "").trim().toUpperCase(Locale.ROOT); String value = p.get("value"); int ttl = p.getInt("ttl", 3600); Integer priority = normalizeNullableInt(p.get("priority")); Integer port = normalizeNullableInt(p.get("port")); Integer weight = normalizeNullableInt(p.get("weight")); if (infonameId <= 0) return ActionResult.err("Invalid infoname_id."); if (!dao.isOwnerOfInfoName(infonameId, userId)) return ActionResult.err("Not owner of this infoname."); if (type.isBlank() || value == null || value.isBlank()) return ActionResult.err("Missing type/value."); RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId); RegistrarDao.InfoNameRow row = null; for (RegistrarDao.InfoNameRow r : owned) { if (r.id() == infonameId) { row = r; break; } } if (row == null) return ActionResult.err("Infoname not found in your ownership list."); if (!RegistrarDao.canUseSubname(row.tln(), userId, sub)) { return ActionResult.err("Subnames are not allowed for this TLN (allow_subdomains=0) unless you own the TLN."); } Integer subId = dao.ensureSubname(infonameId, sub); int rid = dao.addRecord(infonameId, subId, type, value.trim(), ttl, priority, port, weight); return ActionResult.ok("Added record id=" + rid + " type=" + type); } return ActionResult.err("Unknown action: " + action); } private WebResourceResponsePacket render(WebPageContext ctx, int userId, String msg, String err, RegistrarDao dao) throws Exception { RegistrarDao.TlnRow[] tlns = dao.listVisibleTlns(userId); RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId); String tlnHtml = renderTlnSection(tlns, userId); String infoHtml = renderInfoNamesSection(owned); String body = """

INS Registrar

%s %s

Create TLN

TLNs (public + owned)

%s

Create InfoName

Allowed if TLN is public or owned by you.

Add Record

Subname requires allow_subdomains=1 unless you own the TLN. Root (no sub) always allowed.

Your InfoNames

%s
""".formatted( msg == null ? "" : "

" + Html.esc(msg) + "

", err == null ? "" : "

" + Html.esc(err) + "

", tlnHtml, infoHtml ); String html = Html.page("INS Registrar", body); return html(ctx, 200, html); } private String renderTlnSection(RegistrarDao.TlnRow[] tlns, int userId) { if (tlns == null || tlns.length == 0) { return "

No TLNs available.

"; } StringBuilder sb = new StringBuilder(); sb.append(""); return sb.toString(); } private String renderInfoNamesSection(RegistrarDao.InfoNameRow[] owned) { if (owned == null || owned.length == 0) { return "

No infonames yet.

"; } StringBuilder sb = new StringBuilder(); sb.append(""); return sb.toString(); } private WebResourceResponsePacket plain(WebPageContext ctx, int code, String text) { byte[] body = (text == null ? "" : text).getBytes(StandardCharsets.UTF_8); Map headers = HeaderMaps.mutable(); headers.put("content-length", String.valueOf(body.length)); return new WebResourceResponsePacket(outHeader(ctx), code, "text/plain; charset=utf-8", headers, body, null); } private WebResourceResponsePacket html(WebPageContext ctx, int code, String html) { byte[] body = Html.utf8(html); Map headers = HeaderMaps.mutable(); headers.put("content-length", String.valueOf(body.length)); return new WebResourceResponsePacket(outHeader(ctx), code, "text/html; charset=utf-8", headers, body, null); } private WebPacketHeader outHeader(WebPageContext ctx) { WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null; if (in == null) { return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis()); } return new WebPacketHeader( in.getRequestId(), in.getTabId(), in.getPageId(), in.getFrameId(), in.getFlags() | WebPacketFlags.RESOURCE, System.currentTimeMillis() ); } private String safeMsg(Exception e) { String m = e.getMessage(); if (m == null || m.isBlank()) return e.getClass().getSimpleName(); return m; } private record ActionResult(String msg, String err) { static ActionResult ok(String msg) { return new ActionResult(msg, null); } static ActionResult err(String err) { return new ActionResult(null, err); } } }