From daaeac62e266c8211a7760a3eb109798f921d5b6 Mon Sep 17 00:00:00 2001 From: Luis-ADFA Date: Tue, 4 Aug 2026 00:48:08 -0600 Subject: [PATCH 1/4] ADFA-5011 feat(dashboard): in-app rebuild of the dash-node REST core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuild the REST core to the latest branch tip from inside the app, in seconds, without a rootfs rebuild — as a rebuild-only "system module" card in Module Management (ADR-5011, Track A). App orchestrates while the box sleeps (single-proot safe): pdsm stop → git fetch + extracted to /tmp (newest scripts, no working-tree change, uses the clone's auth — nothing bundled in the APK) → preflight gates (clean clone, internet, disk, versions) → rebuild-dashboard.sh (reset --hard → build in staging → smoke-test staged → back up live dist → atomic swap → verify → rollback) → pdsm start. Runs on InstallService's guarded/foreground lifecycle (additive action, reuses the status window) so it can't be killed mid-rebuild. - tools/{preflight,rebuild,dashboard-smoketest}.sh — idempotent, version-independent - DashboardRebuildRunner (+ pure preflight-output parser + unit test) - InstallService: additive ACTION_REBUILD_DASHBOARD; ModuleHubFragment: system card - routes.ts: /system/version (+ Track B rebuild/status seeds); server.ts: env PORT - bump dash-node 1.0.1 → 1.1.0 Note: BRANCH is pinned to the feature branch for pre-merge testing — flip to "main" before merge. --- .../install/presentation/InstallService.java | 39 +++- .../redesign/DashboardRebuildRunner.java | 191 ++++++++++++++++++ .../redesign/ModuleHubFragment.java | 111 ++++++++++ .../app/src/main/res/values/strings_k2go.xml | 11 + .../redesign/DashboardRebuildRunnerTest.java | 57 ++++++ controller/docs/ADR-5011-dashboard-rebuild.md | 96 +++++++++ static/dashboard/package.json | 2 +- static/dashboard/routes.ts | 42 ++++ static/dashboard/server.ts | 4 +- tools/dashboard-smoketest.sh | 36 ++++ tools/preflight-dashboard.sh | 94 +++++++++ tools/rebuild-dashboard.sh | 113 +++++++++++ 12 files changed, 793 insertions(+), 3 deletions(-) create mode 100644 controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java create mode 100644 controller/app/src/test/java/org/iiab/controller/redesign/DashboardRebuildRunnerTest.java create mode 100644 controller/docs/ADR-5011-dashboard-rebuild.md create mode 100755 tools/dashboard-smoketest.sh create mode 100755 tools/preflight-dashboard.sh create mode 100755 tools/rebuild-dashboard.sh diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java index 75cfcc92..4579e27a 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java @@ -66,6 +66,9 @@ public final class InstallService extends Service { public static final String ACTION_CANCEL = "org.iiab.controller.INSTALL_CANCEL"; // Per-module install queue (ADFA-4476 slice 3): distinct from the rootfs ACTION_START. public static final String ACTION_START_MODULES = "org.iiab.controller.INSTALL_START_MODULES"; + /** ADFA-5011: rebuild the dash-node REST core in place (no rootfs rebuild). Reuses this service's + * guard/foreground/status-window so the op can't be killed mid-rebuild. */ + public static final String ACTION_REBUILD_DASHBOARD = "org.iiab.controller.REBUILD_DASHBOARD"; // Broadcast of per-line provisioning output (best-effort in-app log). public static final String ACTION_INSTALL_LOG = "org.iiab.controller.INSTALL_LOG"; @@ -142,6 +145,20 @@ public int onStartCommand(Intent intent, int flags, int startId) { doCancel(); return START_NOT_STICKY; } + if (ACTION_REBUILD_DASHBOARD.equals(action)) { + if (started) return START_NOT_STICKY; + started = true; + rebuildMode = true; + org.iiab.controller.InstallGuard.begin(this); // exclusive: no concurrent proot op + iiabRootDir = new File(getFilesDir(), "rootfs"); + debianRootfs = new File(iiabRootDir, "installed-rootfs/iiab"); + if (prootEngine == null) prootEngine = new PRootEngine(); + startForeground(NOTIFICATION_ID, buildNotification(getString(R.string.k2go_dash_rebuilding))); + acquireHardwareLocks(); + InstallProgressRepository.get().postProvisioning(getString(R.string.k2go_dash_rebuilding)); + new Thread(this::runDashboardRebuild, "dash-rebuild-service").start(); + return START_NOT_STICKY; + } boolean isModules = ACTION_START_MODULES.equals(action); if (!ACTION_START.equals(action) && !isModules) { return START_NOT_STICKY; @@ -806,6 +823,26 @@ private void persistClearQueue() { .putString("pending_modules", "").putBoolean("is_batch_installing", false).apply(); } + // ---------------------------------------------------------------- dashboard rebuild (ADFA-5011) + + /** True while this service is running a dash-node rebuild (skips install-only analytics/finish). */ + private boolean rebuildMode = false; + + /** Drive DashboardRebuildRunner (pdsm stop -> preflight -> rebuild -> pdsm start) on the service's + * guarded, foreground lifecycle. Terminal states reuse finishSuccess()/fail() so the guard, the + * status window and teardown behave exactly like an install. */ + private void runDashboardRebuild() { + new org.iiab.controller.redesign.DashboardRebuildRunner(this, prootEngine, debianRootfs.getAbsolutePath()) + .start(new org.iiab.controller.redesign.DashboardRebuildRunner.Callback() { + @Override public void onLog(String line) { log(line); } + @Override public void onPreflight(org.iiab.controller.redesign.DashboardRebuildRunner.PreflightResult r) { + log("[rebuild] preflight ok=" + r.ok + " installed=" + r.installed + " available=" + r.available); + } + @Override public void onDone() { log("[rebuild] complete"); finishSuccess(); } + @Override public void onError(String reason) { log("[rebuild] error: " + reason); fail(reason); } + }); + } + // ---------------------------------------------------------------- terminal private void finishSuccess() { @@ -814,7 +851,7 @@ private void finishSuccess() { // ADFA-4811: clear the install guard BEFORE publishing SUCCESS, so the UI observer can // start the server for this session (handleServerLaunchClick refuses while the guard is set). org.iiab.controller.InstallGuard.end(this); - if (!resetMode && !moduleMode) { + if (!resetMode && !moduleMode && !rebuildMode) { // ADFA-4466 Phase 1: operational analytics (no-op unless the operator opted in). org.iiab.controller.analytics.AnalyticsClient.with(this) .logInstallCompleted(tier != null ? tier.name() : null, true); diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java new file mode 100644 index 00000000..50d572a7 --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java @@ -0,0 +1,191 @@ +/* + * ============================================================================ + * Name : DashboardRebuildRunner.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5011. App-orchestrated rebuild of the dash-node REST core (Track A of ADR-5011). + * The app is the surgeon and the box is the asleep patient: because + * PRootEngine#executeInContainer launches a proot, we own the rootfs exclusively, so — + * like a module runrole — we STOP the box services first, then work, then START them. + * + * Bootstrap without shipping code in the APK: the newest scripts are pulled straight + * from the on-device clone's remote via `git fetch` + `git show origin/:tools/…` + * into a temp dir (no working-tree change, uses the clone's existing auth — works on a + * private repo). We then RUN them from temp. This works even from an old rootfs whose + * clone predates the scripts, with nothing bundled. + * + * pdsm stop + * -> git fetch + extract preflight/rebuild/smoke to /tmp/k2go + run preflight + * -> (only if preflight OK) run rebuild-dashboard.sh (git reset --hard -> build -> + * staged smoke test -> back up live dist -> atomic swap -> verify -> rollback) + * -> pdsm start + * + * Preflight is non-destructive and gates the rest: it refuses on a dirty clone (so a + * user's local edits are never discarded by the reset), no internet, or low disk. The + * live dashboard is backed up inside rebuild-dashboard.sh; a bad build never ships. + * ============================================================================ + */ +package org.iiab.controller.redesign; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.iiab.controller.PRootEngine; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public final class DashboardRebuildRunner { + + /** PATH-normalized login shell, matching how InstallService invokes commands in the container. */ + private static final String SHELL = + "/usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin bash -lc"; + private static final String CLONE = "/opt/iiab-android"; + private static final String BRANCH = "feat/ADFA-5011-dashboard-rebuild"; // TODO(ADFA-5011): override for pre-merge testing + private static final String TMP = "/tmp/k2go"; // where we drop the newest scripts to run them + + public interface Callback { + void onLog(String line); + /** Preflight finished; {@code r.ok} says whether it is safe to proceed. The rebuild only runs + * if the preflight passed. Reported so the UI can show installed/available + reasons. */ + void onPreflight(PreflightResult r); + void onDone(); + void onError(String reason); + } + + private final Context ctx; + private final PRootEngine engine; + private final String rootfsDir; + + public DashboardRebuildRunner(@NonNull Context ctx, @NonNull PRootEngine engine, @NonNull String rootfsDir) { + this.ctx = ctx.getApplicationContext(); + this.engine = engine; + this.rootfsDir = rootfsDir; + } + + /** Run the full sequence. Callbacks arrive on the PRootEngine worker thread; marshal to the UI. */ + public void start(@NonNull Callback cb) { + cb.onLog("[rebuild] stopping services (exclusive rootfs)…"); + stopServices(() -> bootstrapAndPreflight(cb)); + } + + /** Fetch the newest scripts from the clone's remote into {@code /tmp/k2go} (no working-tree + * change) and run the preflight from there. Nothing destructive happens here. */ + private void bootstrapAndPreflight(Callback cb) { + cb.onLog("[rebuild] fetching latest tools + preflight…"); + final String show = "git show origin/" + BRANCH + ":tools/"; + final String cmd = + "mkdir -p " + TMP + " && cd " + CLONE + + " && git fetch origin " + BRANCH + + " && " + show + "preflight-dashboard.sh > " + TMP + "/preflight.sh" + + " && " + show + "rebuild-dashboard.sh > " + TMP + "/rebuild.sh" + + " && " + show + "dashboard-smoketest.sh > " + TMP + "/smoketest.sh" + + " && sh " + TMP + "/preflight.sh"; + final StringBuilder out = new StringBuilder(); + engine.executeInContainer(ctx, rootfsDir, SHELL + " '" + cmd + "'", + new PRootEngine.OutputListener() { + @Override public void onOutputLine(String line) { out.append(line).append('\n'); cb.onLog(line); } + @Override public void onProcessExit(int exitCode) { + PreflightResult r = PreflightResult.parse(out.toString()); + cb.onPreflight(r); + if (r.ok && exitCode == 0) { + runRebuild(cb); + } else { + // Nothing was touched — wake the box back up and report why. + startServices(() -> cb.onError(r.reasonSummary())); + } + } + @Override public void onError(String error) { startServices(() -> cb.onError(error)); } + }); + } + + private void runRebuild(Callback cb) { + cb.onLog("[rebuild] building + testing + swapping…"); + // Run the temp copy; point it at the temp smoke test so it doesn't need the clone's copy. + final String cmd = "cd " + CLONE + " && K2GO_SMOKE=" + TMP + "/smoketest.sh" + + " K2GO_BRANCH=" + BRANCH + " sh " + TMP + "/rebuild.sh"; + engine.executeInContainer(ctx, rootfsDir, SHELL + " '" + cmd + "'", + new PRootEngine.OutputListener() { + @Override public void onOutputLine(String line) { cb.onLog(line); } + @Override public void onProcessExit(int exitCode) { + // rebuild.sh already restarts dash-node+nginx; bring the rest of the box back too. + startServices(() -> { if (exitCode == 0) cb.onDone(); else cb.onError("rebuild failed"); }); + } + @Override public void onError(String error) { startServices(() -> cb.onError(error)); } + }); + } + + private void stopServices(Runnable then) { pdsm("stop", then); } + private void startServices(Runnable then) { pdsm("start", then); } + + private void pdsm(String action, Runnable then) { + engine.executeInContainer(ctx, rootfsDir, SHELL + " '/usr/local/bin/pdsm " + action + "'", + new PRootEngine.OutputListener() { + @Override public void onOutputLine(String line) { /* noise */ } + @Override public void onProcessExit(int exitCode) { then.run(); } + @Override public void onError(String error) { then.run(); } // best-effort; never wedge + }); + } + + /** + * Pure parse of the preflight's machine-readable line. No Android deps → unit-testable. Reads the + * LAST {@code PREFLIGHT_RESULT={json}} line in the output. If none/invalid, treats it as not-OK. + */ + public static final class PreflightResult { + public final boolean ok; + public final String installed; + public final String available; + public final boolean updateAvailable; + public final List reasons; + + public PreflightResult(boolean ok, String installed, String available, + boolean updateAvailable, List reasons) { + this.ok = ok; + this.installed = installed; + this.available = available; + this.updateAvailable = updateAvailable; + this.reasons = reasons; + } + + public String reasonSummary() { + if (reasons == null || reasons.isEmpty()) return "preflight failed"; + StringBuilder sb = new StringBuilder(); // String.join is API 26+; minSdk is 24 + for (int i = 0; i < reasons.size(); i++) { + if (i > 0) sb.append(", "); + sb.append(reasons.get(i)); + } + return sb.toString(); + } + + public static PreflightResult parse(String output) { + String json = null; + if (output != null) { + for (String line : output.split("\n")) { + String t = line.trim(); + if (t.startsWith("PREFLIGHT_RESULT=")) json = t.substring("PREFLIGHT_RESULT=".length()).trim(); + } + } + if (json == null) return new PreflightResult(false, "unknown", "unknown", false, + listOf("no_preflight_output")); + try { + JSONObject o = new JSONObject(json); + List reasons = new ArrayList<>(); + JSONArray ra = o.optJSONArray("reasons"); + if (ra != null) for (int i = 0; i < ra.length(); i++) reasons.add(ra.optString(i, "")); + return new PreflightResult( + o.optBoolean("ok", false), + o.optString("installed", "unknown"), + o.optString("available", "unknown"), + o.optBoolean("update_available", false), + reasons); + } catch (Exception e) { + return new PreflightResult(false, "unknown", "unknown", false, listOf("bad_preflight_json")); + } + } + + private static List listOf(String s) { List l = new ArrayList<>(); l.add(s); return l; } + } +} diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java index c3d9e7d3..1eef4a29 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java @@ -148,6 +148,10 @@ private void buildCards() { helperLp.bottomMargin = px(8); host.addView(helper, helperLp); + // ADFA-5011: the dash-node REST core is a system module — always present (not installable/ + // removable), with a single Rebuild action. Shown at the top, above the installable list. + addSystemDashboardCard(); + List items = new ArrayList<>(); for (ModuleCards.Card c : ModuleCards.all()) if (installable.contains(c.key())) items.add(c); @@ -312,6 +316,113 @@ private View cardRow(final ModuleCards.Card c) { return row; } + // ---- ADFA-5011: dash-node "system module" card (Rebuild-only) ------------------------------- + + private void addSystemDashboardCard() { + LinearLayout row = new LinearLayout(requireContext()); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setGravity(Gravity.CENTER_VERTICAL); + row.setBackgroundResource(R.drawable.k2go_card_bg); + row.setPadding(px(16), px(14), px(16), px(14)); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + lp.bottomMargin = px(12); + row.setLayoutParams(lp); + + LinearLayout col = new LinearLayout(requireContext()); + col.setOrientation(LinearLayout.VERTICAL); + TextView title = new TextView(requireContext()); + title.setText(R.string.k2go_dash_card_title); + title.setTypeface(title.getTypeface(), android.graphics.Typeface.BOLD); + title.setTextColor(ContextCompat.getColor(requireContext(), R.color.k2go_ink)); + title.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_TitleMedium); + col.addView(title); + TextView sub = new TextView(requireContext()); + sub.setText(R.string.k2go_dash_version_unknown); + sub.setTextColor(ContextCompat.getColor(requireContext(), R.color.k2go_muted)); + sub.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall); + col.addView(sub); + row.addView(col, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); + + TextView rebuild = statePill(getString(R.string.k2go_dash_rebuild), R.color.k2go_teal); + rebuild.setPadding(px(14), px(6), px(14), px(6)); + rebuild.setOnClickListener(v -> onRebuildClicked()); + LinearLayout.LayoutParams tlp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + tlp.leftMargin = px(10); + row.addView(rebuild, tlp); + + host.addView(row); + fetchDashVersion(sub); + } + + private void onRebuildClicked() { + if (org.iiab.controller.env.EnvironmentLock.isHeld(requireContext())) { + Snackbars.make(host, R.string.k2go_install_busy).show(); + return; + } + if (!hasInternet()) { + Snackbars.make(host, R.string.k2go_dash_needs_internet).show(); + return; + } + new com.google.android.material.dialog.MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.k2go_dash_rebuild_confirm_title) + .setMessage(R.string.k2go_dash_rebuild_confirm_msg) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.k2go_dash_rebuild, (d, w) -> startRebuild()) + .show(); + } + + private void startRebuild() { + android.content.Intent svc = new android.content.Intent(requireContext(), + org.iiab.controller.install.presentation.InstallService.class) + .setAction(org.iiab.controller.install.presentation.InstallService.ACTION_REBUILD_DASHBOARD); + ContextCompat.startForegroundService(requireContext(), svc); + // Open the guarded status window (same one installs use): it blocks leaving mid-op and + // returns here on re-entry, driven by InstallProgressRepository. + startActivity(new android.content.Intent(requireContext(), SetupProgressActivity.class)); + } + + /** Best-effort: show the installed dash-node version on the card (GET /system/version). */ + private void fetchDashVersion(final TextView sub) { + AppExecutors.get().io().execute(() -> { + String v = null; + HttpURLConnection c = null; + try { + URL u = new URL(BoxEndpoints.API + "/system/version"); + c = (HttpURLConnection) u.openConnection(); + c.setUseCaches(false); + c.setConnectTimeout(1500); + c.setReadTimeout(1500); + if (c.getResponseCode() == 200) { + java.io.BufferedReader r = new java.io.BufferedReader(new java.io.InputStreamReader(c.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = r.readLine()) != null) sb.append(line); + r.close(); + v = new org.json.JSONObject(sb.toString()).optString("version", null); + } + } catch (Exception ignored) { + } finally { + if (c != null) c.disconnect(); + } + final String ver = v; + main.post(() -> { + if (isAdded() && ver != null && !ver.isEmpty()) sub.setText(getString(R.string.k2go_dash_card_sub_fmt, ver)); + }); + }); + } + + private boolean hasInternet() { + android.net.ConnectivityManager cm = (android.net.ConnectivityManager) + requireContext().getSystemService(android.content.Context.CONNECTIVITY_SERVICE); + if (cm == null) return true; // can't tell → let the preflight decide + android.net.Network n = cm.getActiveNetwork(); + if (n == null) return false; + android.net.NetworkCapabilities caps = cm.getNetworkCapabilities(n); + return caps != null && caps.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + private void refreshProceed() { if (proceed == null || !isAdded()) return; int n = ModuleWishlist.size(requireContext()); diff --git a/controller/app/src/main/res/values/strings_k2go.xml b/controller/app/src/main/res/values/strings_k2go.xml index 3f0bb85a..63cb659a 100644 --- a/controller/app/src/main/res/values/strings_k2go.xml +++ b/controller/app/src/main/res/values/strings_k2go.xml @@ -712,6 +712,17 @@ Tap to open · tick to install several at once. + + Rebuilding dashboard… + Dashboard (REST API) + REST core + + REST core · installed v%1$s + Rebuild + Rebuild the REST core? + Updates dash-node to the latest version. The server is briefly unavailable while it rebuilds; don\'t leave this screen until it finishes. + Rebuild needs an internet connection. + Manage downloads Get more content Downloaded content diff --git a/controller/app/src/test/java/org/iiab/controller/redesign/DashboardRebuildRunnerTest.java b/controller/app/src/test/java/org/iiab/controller/redesign/DashboardRebuildRunnerTest.java new file mode 100644 index 00000000..fc67098a --- /dev/null +++ b/controller/app/src/test/java/org/iiab/controller/redesign/DashboardRebuildRunnerTest.java @@ -0,0 +1,57 @@ +package org.iiab.controller.redesign; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.iiab.controller.redesign.DashboardRebuildRunner.PreflightResult; +import org.junit.Test; + +/** Pure-JVM tests for the preflight-output parser (ADFA-5011). Uses the real org.json on the test + * classpath; no Android deps. */ +public class DashboardRebuildRunnerTest { + + @Test public void parsesOkResultWithVersions() { + String out = "[preflight] fetch OK\n" + + "PREFLIGHT_RESULT={\"ok\":true,\"installed\":\"1.0.1\",\"available\":\"1.1.0\",\"update_available\":true,\"reasons\":[]}\n"; + PreflightResult r = PreflightResult.parse(out); + assertTrue(r.ok); + assertEquals("1.0.1", r.installed); + assertEquals("1.1.0", r.available); + assertTrue(r.updateAvailable); + assertTrue(r.reasons.isEmpty()); + } + + @Test public void parsesFailureWithReasons() { + String out = "PREFLIGHT_RESULT={\"ok\":false,\"installed\":\"1.0.1\",\"available\":\"unknown\"," + + "\"update_available\":false,\"reasons\":[\"dirty_worktree\",\"fetch_failed\"]}"; + PreflightResult r = PreflightResult.parse(out); + assertFalse(r.ok); + assertEquals(2, r.reasons.size()); + assertEquals("dirty_worktree, fetch_failed", r.reasonSummary()); + } + + @Test public void takesLastResultLineWhenRepeated() { + String out = "PREFLIGHT_RESULT={\"ok\":false,\"reasons\":[\"x\"]}\n" + + "PREFLIGHT_RESULT={\"ok\":true,\"installed\":\"1.1.0\",\"available\":\"1.1.0\",\"update_available\":false,\"reasons\":[]}\n"; + PreflightResult r = PreflightResult.parse(out); + assertTrue(r.ok); + assertEquals("1.1.0", r.installed); + } + + @Test public void missingLineIsNotOk() { + PreflightResult r = PreflightResult.parse("some logs\nno verdict here\n"); + assertFalse(r.ok); + assertEquals("no_preflight_output", r.reasonSummary()); + } + + @Test public void malformedJsonIsNotOk() { + PreflightResult r = PreflightResult.parse("PREFLIGHT_RESULT={not json}"); + assertFalse(r.ok); + assertEquals("bad_preflight_json", r.reasonSummary()); + } + + @Test public void nullOutputIsNotOk() { + assertFalse(PreflightResult.parse(null).ok); + } +} diff --git a/controller/docs/ADR-5011-dashboard-rebuild.md b/controller/docs/ADR-5011-dashboard-rebuild.md new file mode 100644 index 00000000..6ca224c8 --- /dev/null +++ b/controller/docs/ADR-5011-dashboard-rebuild.md @@ -0,0 +1,96 @@ +# ADR-5011 — Rebuilding the dash-node REST core without a rootfs rebuild + +Status: Proposed (ADFA-5011). **Track A** (app-orchestrated, box-asleep rebuild) targeted for this PR; +**Track B** (REST self-update toolchain) seeded now, matured incrementally toward v2. + +## Context + +- dash-node (the in-server REST core: Express on `127.0.0.1:4000`, fronted by nginx, run by `pdsm`) is + just compiled TypeScript under `/library/dashboard`. Yet updating a few of those files currently + requires the **full ~2h rootfs rebuild**, because that is the only shipping path. `tools/dev-push-dashboard.sh` + already does an in-place update on the box, but only as a **manual** dev step run from a terminal + inside the proot — most users can't do that. +- **Single proot (from ADR-4832).** `PRootEngine` has no mutual exclusion: a second concurrent proot + operation over the same rootfs collides (shared `/tmp`, `/dev/shm`, ports, service restarts, global + `killall -9 proot`) and corrupts state. proot also **cannot be entered after start**. And you can't + "just kill the REST": stopping dash-node/proot takes the **whole** engine down (kiwix-serve, nginx, + everything). So any self-update must respect "one proot op at a time". +- **dashboard is core, not a user module (ADFA-4842).** It is the REST API that maps FQR and all + content downloads depend on — not installable/removable like books/maps/kolibri. +- dash-node **stalled at 1.0.1** through many structural changes: there was no versioning discipline, + so "installed version" told users nothing. + +## Decision + +### Track A — app-orchestrated, box-asleep rebuild (primary; this PR) + +1. **The app is the surgeon; the box is the asleep patient.** This inverts the normal pattern (box + orchestrates, app observes): for a self-update, the app drives a **gated sequence** of proot + commands and reads each output to decide whether to advance. Everything proot is **down** during + the op (accepted; it is brief), which sidesteps the single-proot constraint entirely — there is + never a second, overlapping proot. +2. **App-provided, idempotent, version-independent scripts.** The app carries its own tools (bundled / + pushed), so the rebuild does **not** depend on what tooling the box already has, nor on the REST + API being reachable. This is what makes it work from **any** installed version, including 1.0.1 — + there is no bootstrap chicken-and-egg. Scripts must be idempotent and safe across 1.0.1 / 1.1 / + 1.2 / 2.0, like the upstream iiab/iiab Ansible roles the other modules run. +3. **Gated sequence (2–3 steps), each verified before the next:** + - **Preflight + backup** (`tools/preflight-dashboard.sh`): non-destructively confirm `/opt/iiab-android` + is a clean git repo on `main` that can `git fetch`, that the required tools exist, that there is + disk headroom, and report installed vs available version. If anything obstructs → abort with a + clear reason; **nothing is touched**. + - **Fetch + build + test** (`tools/rebuild-dashboard.sh`): `git reset --hard origin/main` → build in + a **staging** dir → **smoke-test the staged build** on a temp port (`tools/dashboard-smoketest.sh`). + - **Swap + verify**: promote the staged `dist` only if it passed → restart dash-node → re-verify + live; on any failure **roll back** to the step-1 backup and leave the box as it was. +4. **Reuse the module pipeline + gates.** Run through `PRootEngine` under `InstallGuard` with the + module **status-window** UX: the user **cannot leave mid-rebuild**, and re-entering returns to the + live status window (same protection as an Ansible module install). Scope the stop to dash-node + where possible rather than a full engine teardown. +5. **Surface as a rebuild-only "system module" card** (module template like matomo/maps/kolibri, but + the only action is **Rebuild** — no install/remove/hide). This keeps dashboard as core (ADFA-4842) + while giving it a home consistent with the other modules. The card shows **installed vs available** + version and flags "behind". +6. **Versioning.** Bump dash-node `1.0.1 → 1.1.0`; increment per change from here. Installed version is + read from `package.json` **via proot** (present in every version — version-independent), not from a + REST endpoint. The in-app rebuild is available **from 1.1.0 on**; a box still on 1.0.1 bridges once + via a normal update/install (or the manual `dev-push` script), then is self-service. Rebuilds always + jump to the tip of `main` (not incremental). + +### Track B — REST self-update toolchain (secondary; seeded now, matured later) + +7. dash-node progressively gains its **own** ability to update itself "consciously": a toolset that can + answer "do I have everything I need?" and "did my own tests pass?" and otherwise refuse. Seeded in + this PR as **groundwork, not the primary path**: `GET /system/version`, `POST /system/dashboard/rebuild` + (detached), `GET /system/dashboard/rebuild/status`, and the smoke test. These are kept but **not + relied on** for the shipping flow (they carry the self-reference + bootstrap problems Track A + avoids). Over time (toward v2) this can become the fast path for boxes already on a capable version, + layered on top of the safe app-orchestrated floor. + +## Consequences / caveats + +- **Downtime:** the whole proot engine is down for the rebuild (~1–2 min). Accepted trade-off for + reliability and single-proot safety. It is still far cheaper than the ~2h rootfs rebuild. +- **Inverted control:** the app orchestrates step-by-step while the box is passive — deliberate, and + the opposite of the content-download flow (box-owned durable jobs, ADR-4832). +- **Idempotent/version-independent scripts are a hard requirement**, or an update from an old version + could wedge a half-applied state. The build→test→swap→rollback structure means a failed build never + ships; the previous `dist` is restored. +- Track B's REST endpoints exist but must not be advertised as the way to trigger a rebuild yet. + +## Alternatives considered + +- **REST-alive self-rebuild ("local anesthesia").** Minimal downtime, but the REST call rebuilds the + process serving it (self-reference → needs `setsid` detachment) and depends on the endpoint already + existing (bootstrap: absent on 1.0.1). Rejected as the primary path; retained as the Track B seed. +- **Full engine teardown via a dedicated Ansible role.** Unnecessary — we only rebuild dash-node and do + not need a second proot; the app-orchestrated sequence is lighter and sufficient. +- **A separate command server (cmdsrv).** Already rejected in ADR-4832; still applies. + +## References + +`tools/dev-push-dashboard.sh`, `install_iiaboa_dashboard` (top-level `iiab-android`), +`static/dashboard/{server.ts,routes.ts,package.json}`, `tools/rebuild-dashboard.sh`, +`tools/dashboard-smoketest.sh`, `tools/preflight-dashboard.sh`, `PRootEngine`, +`InstallService`/`InstallGuard`, `ModuleRegistry` (ADFA-4842: dashboard-is-core), ADR-4832 +(single proot / in-server channel). diff --git a/static/dashboard/package.json b/static/dashboard/package.json index 28f7fce3..f6fecf96 100644 --- a/static/dashboard/package.json +++ b/static/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "dashboard-console", - "version": "1.0.1", + "version": "1.1.0", "description": "", "main": "index.js", "scripts": { diff --git a/static/dashboard/routes.ts b/static/dashboard/routes.ts index ccad623f..90adece7 100644 --- a/static/dashboard/routes.ts +++ b/static/dashboard/routes.ts @@ -234,6 +234,48 @@ apiRouter.post('/kiwix/delete', (req: Request, res: Response): void => { idx.on('exit', () => { if (!res.headersSent) res.json({ ok: true, reindexed: true }); }); }); +// --- System: dash-node version + self-rebuild (ADFA-5011) ------------------------------------- +// The dashboard REST core can rebuild ITSELF from the on-device clone without a rootfs rebuild: +// git fetch+reset -> build in a staging dir -> smoke-test the staged build -> atomically swap it +// live only if it passes (tools/rebuild-dashboard.sh). The rebuild runs DETACHED (setsid) so +// restarting dash-node mid-run never kills it. Declared before the generic /:type/* routes. +const REBUILD_SCRIPT = '/opt/iiab-android/tools/rebuild-dashboard.sh'; +const REBUILD_STATUS_FILE = '/var/run/dash-rebuild.status'; + +// Installed dash-node version (from package.json), so the module card can show it + compare. +apiRouter.get('/system/version', (_req: Request, res: Response): void => { + try { + const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')); + res.json({ version: String(pkg.version || 'unknown') }); + } catch (e: any) { + res.status(500).json({ error: e?.message || 'version read failed' }); + } +}); + +// Current rebuild state: idle | running | done | error (read from the status file the script writes). +apiRouter.get('/system/dashboard/rebuild/status', (_req: Request, res: Response): void => { + let state = 'idle'; + try { state = (fs.readFileSync(REBUILD_STATUS_FILE, 'utf8').trim() || 'idle'); } catch { /* no file yet */ } + res.json({ state }); +}); + +// Trigger a rebuild. Fire-and-forget: launches the orchestrator DETACHED and returns 202 at once; +// the app then polls /system/version + RestReadiness until the API is back on the new version. +apiRouter.post('/system/dashboard/rebuild', (_req: Request, res: Response): void => { + let running = false; + try { running = fs.readFileSync(REBUILD_STATUS_FILE, 'utf8').trim() === 'running'; } catch { /* none */ } + if (running) { res.status(409).json({ error: 'a rebuild is already running' }); return; } + if (!fs.existsSync(REBUILD_SCRIPT)) { res.status(500).json({ error: 'rebuild script not found' }); return; } + try { + // setsid => own session, so `pdsm restart dash-node` inside the script can't kill this run. + const child = spawn('setsid', ['sh', REBUILD_SCRIPT], { detached: true, stdio: 'ignore' }); + child.unref(); + res.status(202).json({ ok: true, state: 'running' }); + } catch (e: any) { + res.status(500).json({ error: e?.message || 'could not start rebuild' }); + } +}); + // --- Kolibri: readiness, catálogo y selección (ADFA-4949) ------------------------- // Consultas directas (no-job). La descarga en sí es un job durable // (POST /kolibri/download), que sale gratis al añadir 'kolibri' a VALID_TYPES. diff --git a/static/dashboard/server.ts b/static/dashboard/server.ts index 4be3b614..a872df11 100644 --- a/static/dashboard/server.ts +++ b/static/dashboard/server.ts @@ -26,7 +26,9 @@ app.use(helmet({ app.use(express.json()); app.use('/api', apiRouter); -const PORT = 4000; +// ADFA-5011: PORT is env-overridable so a staged build can be smoke-tested on a temp port +// (e.g. 4010) before it is swapped live on 4000 — see tools/rebuild-dashboard.sh. +const PORT = Number(process.env.PORT) || 4000; // ADFA-4839/4933: bind to loopback only. nginx (localhost) proxies /k2go-api to us; // there is no reason to expose :4000 on the device's network interfaces. server.listen(PORT, '127.0.0.1', () => { diff --git a/tools/dashboard-smoketest.sh b/tools/dashboard-smoketest.sh new file mode 100755 index 00000000..236cdca3 --- /dev/null +++ b/tools/dashboard-smoketest.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# tools/dashboard-smoketest.sh BASE_URL — ADFA-5011 +# +# Fast, critical-path health check for a dash-node build. Used twice by +# tools/rebuild-dashboard.sh: against the STAGED build (temp port) before promoting it, +# and against the LIVE build after the swap. Exit 0 = healthy; non-zero aborts the rebuild +# (staged) or triggers rollback (live). +# +# Keep it LEAN — only critical, side-effect-free GET endpoints, so it stays seconds. When a +# change adds a critical endpoint, add ONE check here; don't mirror the whole API. +# +# sh tools/dashboard-smoketest.sh http://127.0.0.1:4010/api +set -u + +BASE="${1:?usage: dashboard-smoketest.sh BASE_URL}" + +fail() { echo "smoketest FAIL: $*" >&2; exit 1; } + +# GET PATH — healthy if it answers with any non-5xx (2xx/3xx/4xx = the app is up and routing). +check() { + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "$BASE$1") || fail "no response: $1" + [ -n "$code" ] && [ "$code" -lt 500 ] || fail "$1 -> HTTP ${code:-none}" + echo " ok $1 ($code)" +} + +# 1) version endpoint must answer with a non-empty version (proves the new build is the one running). +body=$(curl -s --max-time 8 "$BASE/system/version") || fail "version endpoint unreachable" +echo "$body" | grep -q '"version"' || fail "version payload malformed: $body" + +# 2) critical read paths (no side effects). +check /system/version +check /books/library +check /kiwix/library +check /books/languages + +echo "smoketest OK ($BASE)" diff --git a/tools/preflight-dashboard.sh b/tools/preflight-dashboard.sh new file mode 100755 index 00000000..419506e1 --- /dev/null +++ b/tools/preflight-dashboard.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# tools/preflight-dashboard.sh [CLONE_DIR] [BRANCH] — ADFA-5011 +# +# Step 1 of the app-orchestrated dash-node rebuild (see ADR-5011). The APP runs this INSIDE the proot +# and reads its output to decide go/no-go BEFORE anything is changed. It is strictly NON-DESTRUCTIVE: +# the only network/state touch is `git fetch` (updates remote-tracking refs; never the working tree, +# never a build, never a restart). Idempotent and version-independent — safe to run on any installed +# dash-node (1.0.1, 1.1, ...). +# +# Output: human "[preflight] ..." lines for the log, then ONE machine-readable line the app parses: +# PREFLIGHT_RESULT={"ok":true|false,"installed":"x","available":"y","update_available":bool,"reasons":[...]} +# Exit 0 = safe to proceed to the rebuild; non-zero = do NOT proceed. +# +# sh tools/preflight-dashboard.sh /opt/iiab-android main +set -u + +CLONE_DIR="${1:-/opt/iiab-android}" +BRANCH="${2:-main}" +LIVE="/library/dashboard" +MIN_FREE_MB="${K2GO_REBUILD_MIN_FREE_MB:-800}" + +ok=1 +reasons="" +add_reason() { reasons="${reasons:+$reasons|}$1"; ok=0; } +say() { echo "[preflight] $*"; } + +# Extract "version" from a package.json fed on stdin (no jq dependency). +pkg_version() { + grep '"version"' | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' +} + +is_repo=0 +if git -C "$CLONE_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + is_repo=1 +else + add_reason "no_git_repo"; say "FAIL: $CLONE_DIR is not a git repo" +fi + +# Required tools (the surgeon needs these present in the proot). +for t in git node yarn tar; do + command -v "$t" >/dev/null 2>&1 || { add_reason "missing_tool:$t"; say "FAIL: missing tool: $t"; } +done +{ command -v pdsm >/dev/null 2>&1 || [ -x /usr/local/bin/pdsm ]; } || { add_reason "missing_tool:pdsm"; say "FAIL: missing tool: pdsm"; } + +# The live install must exist (we never create it here). +[ -d "$LIVE" ] || { add_reason "no_live_dashboard"; say "FAIL: $LIVE not found (dashboard not installed?)"; } + +if [ "$is_repo" -eq 1 ]; then + # Clean working tree — a rebuild does `git reset --hard`, so refuse if the user has local edits. + if [ -n "$(git -C "$CLONE_DIR" status --porcelain 2>/dev/null)" ]; then + add_reason "dirty_worktree"; say "FAIL: local modifications in $CLONE_DIR (would be discarded)" + fi + cur=$(git -C "$CLONE_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?") + [ "$cur" = "$BRANCH" ] || say "note: on branch '$cur' (rebuild will switch to '$BRANCH')" + # Network + remote reachable: fetch is non-destructive (updates refs only). + if git -C "$CLONE_DIR" fetch origin "$BRANCH" >/dev/null 2>&1; then + say "fetch OK (origin/$BRANCH)" + else + add_reason "fetch_failed"; say "FAIL: git fetch origin $BRANCH (offline or unreachable?)" + fi +fi + +# Disk headroom for a staging build. +free_mb=$(df -Pm "$LIVE" 2>/dev/null | awk 'NR==2 {print $4}') +if [ -n "$free_mb" ] && [ "$free_mb" -ge "$MIN_FREE_MB" ]; then + say "disk OK (${free_mb}MB free)" +else + add_reason "low_disk:${free_mb:-unknown}"; say "FAIL: low disk (${free_mb:-?}MB < ${MIN_FREE_MB}MB)" +fi + +# Versions: installed (on box) vs available (tip of origin/BRANCH). +installed="unknown" +[ -f "$LIVE/package.json" ] && installed=$(pkg_version < "$LIVE/package.json") +[ -n "$installed" ] || installed="unknown" +available="unknown" +if [ "$is_repo" -eq 1 ]; then + v=$(git -C "$CLONE_DIR" show "origin/$BRANCH:static/dashboard/package.json" 2>/dev/null | pkg_version) + [ -n "$v" ] && available="$v" +fi +update_available=false +[ "$available" != "unknown" ] && [ "$installed" != "$available" ] && update_available=true +say "installed=$installed available=$available update_available=$update_available" + +# Machine-readable verdict (single line the app greps for). +[ "$ok" -eq 1 ] && okj=true || okj=false +rj="" +if [ -n "$reasons" ]; then + oldIFS=$IFS; IFS='|' + for r in $reasons; do rj="${rj:+$rj,}\"$r\""; done + IFS=$oldIFS +fi +echo "PREFLIGHT_RESULT={\"ok\":$okj,\"installed\":\"$installed\",\"available\":\"$available\",\"update_available\":$update_available,\"reasons\":[$rj]}" + +[ "$ok" -eq 1 ] diff --git a/tools/rebuild-dashboard.sh b/tools/rebuild-dashboard.sh new file mode 100755 index 00000000..560ea497 --- /dev/null +++ b/tools/rebuild-dashboard.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# tools/rebuild-dashboard.sh [CLONE_DIR] — ADFA-5011 +# +# Rebuild ONLY the dash-node REST API from the on-device clone, without a rootfs rebuild. +# Blue-green + verify-before-swap so the live API is never left in a broken state: +# +# 1. git fetch + reset --hard origin/ (deterministic; the box clone isn't edited) +# 2. build in a STAGING dir (yarn install + build) +# 3. smoke-test the STAGED build on a temp port (tools/dashboard-smoketest.sh) +# 4. only if it passes: back up live dist, atomically swap staged dist in, sync source + nginx, +# restart dash-node + nginx, and re-verify LIVE +# 5. if the live check fails: roll back to the backed-up dist and restart +# +# If step 1-3 fail, the LIVE dashboard is never touched. Launched DETACHED (setsid) by +# POST /api/system/dashboard/rebuild, so the `pdsm restart dash-node` in step 4 cannot kill +# this script. Single-flight via a lock dir; progress in $LOG; state in $STATUS for the app. +set -u + +CLONE_DIR="${1:-/opt/iiab-android}" +BRANCH="${K2GO_BRANCH:-main}" +SRC="$CLONE_DIR/static/dashboard" +LIVE="/library/dashboard" +STAGE="/library/dashboard.staging" +BACKUP="/library/dashboard.dist.bak" +NGINX_CONF_DIR="/etc/nginx/conf.d" +TEST_PORT="${K2GO_REBUILD_TEST_PORT:-4010}" +# The app runs the newest scripts from a temp dir (extracted via `git show`), so it can point us at +# the matching smoke test there; falls back to the clone's copy for a manual/dev run. +SMOKE="${K2GO_SMOKE:-$CLONE_DIR/tools/dashboard-smoketest.sh}" + +STATUS="/var/run/dash-rebuild.status" +LOG="/var/log/dash-rebuild.log" +LOCK="/var/run/dash-rebuild.lock" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG" 2>/dev/null; } +set_status() { echo "$1" > "$STATUS" 2>/dev/null || true; } +cleanup() { [ -n "${TESTPID:-}" ] && kill "$TESTPID" 2>/dev/null || true; rm -rf "$STAGE"; rmdir "$LOCK" 2>/dev/null || true; } +fail() { log "FAIL: $*"; set_status "error"; cleanup; exit 1; } + +# Single-flight: mkdir is atomic. +mkdir "$LOCK" 2>/dev/null || { echo "another rebuild is running" >&2; exit 3; } +trap cleanup EXIT +: > "$LOG" 2>/dev/null || true +set_status "running" +log "rebuild start (branch=$BRANCH, clone=$CLONE_DIR)" + +[ -d "$SRC" ] || fail "source $SRC not found" +[ -d "$LIVE" ] || fail "live $LIVE not found (dashboard not installed?)" +[ -f "$SMOKE" ] || fail "smoke test $SMOKE not found" + +# 1) refresh the clone to the tip of the branch (deterministic; discards any local drift). +log "git fetch + reset --hard origin/$BRANCH" +git -C "$CLONE_DIR" fetch origin "$BRANCH" >>"$LOG" 2>&1 || fail "git fetch (offline?)" +git -C "$CLONE_DIR" reset --hard "origin/$BRANCH" >>"$LOG" 2>&1 || fail "git reset" + +# 2) build in staging. Reuse live node_modules to speed the install; build fresh dist. +log "staging build" +rm -rf "$STAGE"; mkdir -p "$STAGE" || fail "mkdir staging" +( cd "$SRC" && tar --exclude=node_modules --exclude=dist -cf - . ) | ( cd "$STAGE" && tar -xf - ) || fail "copy source to staging" +[ -d "$LIVE/node_modules" ] && cp -a "$LIVE/node_modules" "$STAGE/node_modules" +( cd "$STAGE" && yarn install >>"$LOG" 2>&1 && yarn build >>"$LOG" 2>&1 ) || fail "yarn install/build (offline or build error) — live untouched" +[ -f "$STAGE/dist/server.js" ] || fail "no dist/server.js after build — live untouched" + +# 3) smoke-test the STAGED build on a temp port (does not touch the live :4000). +log "smoke test staged build on :$TEST_PORT" +( cd "$STAGE" && PORT="$TEST_PORT" node dist/server.js >>"$LOG" 2>&1 ) & +TESTPID=$! +sleep 3 +sh "$SMOKE" "http://127.0.0.1:$TEST_PORT/api" >>"$LOG" 2>&1 +RC=$? +kill "$TESTPID" 2>/dev/null || true; wait "$TESTPID" 2>/dev/null || true; TESTPID="" +[ "$RC" -eq 0 ] || fail "staged smoke test failed (rc=$RC) — NOT promoting; live untouched" + +# 4) promote: back up live dist, swap staged in, sync source + nginx, restart. +log "staged build passed — promoting" +rm -rf "$BACKUP" +[ -d "$LIVE/dist" ] && cp -a "$LIVE/dist" "$BACKUP" +# Sync source (so the next build matches) — additive tar after dropping pure-source subdirs; +# runtime state (node_modules, *.sqlite3 job storage, books/catalog.db) is left in place. +for d in sockets views public test; do rm -rf "$LIVE/$d"; done +( cd "$STAGE" && tar --exclude=node_modules --exclude=dist -cf - . ) | ( cd "$LIVE" && tar -xf - ) || fail "sync source to live" +cp -a "$STAGE/node_modules/." "$LIVE/node_modules/" 2>/dev/null || true +# The dist swap is the near-atomic, restart-critical step (dash-node runs dist/server.js). +rm -rf "$LIVE/dist" && cp -a "$STAGE/dist" "$LIVE/dist" || fail "dist swap" +# nginx reads /etc/nginx/conf.d, not /library/dashboard, so mirror the vhost. +[ -f "$LIVE/dash-node-nginx.conf" ] && { cp -f "$LIVE/dash-node-nginx.conf" "$NGINX_CONF_DIR/dash-node-nginx.conf"; chmod 0600 "$NGINX_CONF_DIR/dash-node-nginx.conf"; } + +log "restart dash-node + nginx" +/usr/local/bin/pdsm restart dash-node >>"$LOG" 2>&1 || log "warn: pdsm restart dash-node returned non-zero" +/usr/local/bin/pdsm restart nginx >>"$LOG" 2>&1 || log "warn: pdsm restart nginx returned non-zero" + +# 5) verify LIVE; roll back the dist if it doesn't come up. +log "verifying live :4000" +ok=0 +i=1 +while [ "$i" -le 15 ]; do + if sh "$SMOKE" "http://127.0.0.1:4000/api" >>"$LOG" 2>&1; then ok=1; break; fi + sleep 2; i=$((i + 1)) +done +if [ "$ok" -eq 1 ]; then + log "live OK — rebuild complete" + rm -rf "$BACKUP" + set_status "done" +else + log "live check FAILED after swap — rolling back dist" + if [ -d "$BACKUP" ]; then + rm -rf "$LIVE/dist" && cp -a "$BACKUP" "$LIVE/dist" + /usr/local/bin/pdsm restart dash-node >>"$LOG" 2>&1 || true + log "rolled back to previous dist" + fi + set_status "error" +fi +cleanup From 8c9ac2661d02492ffaaa7863db0769e6e3512e18 Mon Sep 17 00:00:00 2001 From: Luis-ADFA Date: Tue, 4 Aug 2026 10:20:34 -0600 Subject: [PATCH 2/4] ADFA-5011 feat(dashboard): rebuild detail card + keep progress on screen until the box is live --- .../InstallProgressRepository.java | 3 + .../install/presentation/InstallService.java | 3 + .../install/presentation/InstallState.java | 6 +- .../redesign/DashboardDetailFragment.java | 111 ++++++++++ .../controller/redesign/DashboardRebuild.java | 74 +++++++ .../redesign/DashboardRebuildRunner.java | 31 ++- .../controller/redesign/DashboardVersion.java | 46 +++++ .../redesign/ModuleDetailFragment.java | 3 +- .../redesign/ModuleHubFragment.java | 78 ++----- .../redesign/SetupLibraryActivity.java | 8 + .../redesign/SetupProgressActivity.java | 191 ++++++++++++++++++ .../layout/fragment_k2go_module_detail.xml | 7 +- .../app/src/main/res/values/strings_k2go.xml | 13 ++ 13 files changed, 498 insertions(+), 76 deletions(-) create mode 100644 controller/app/src/main/java/org/iiab/controller/redesign/DashboardDetailFragment.java create mode 100644 controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuild.java create mode 100644 controller/app/src/main/java/org/iiab/controller/redesign/DashboardVersion.java diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallProgressRepository.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallProgressRepository.java index 13bb823c..2ece2488 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallProgressRepository.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallProgressRepository.java @@ -40,6 +40,9 @@ private InstallProgressRepository() { /** Marks the upcoming posts as belonging to the scratch-reset pipeline. */ public void beginReset() { currentOp = InstallState.Op.RESET; } + /** ADFA-5011: marks the upcoming posts as belonging to a dash-node REST-core rebuild. */ + public void beginRebuild() { currentOp = InstallState.Op.REBUILD; } + /** The operation the current state belongs to (INSTALL when idle). */ public InstallState.Op currentOp() { return current().op; } diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java index 4579e27a..11a2c5ae 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java @@ -155,6 +155,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (prootEngine == null) prootEngine = new PRootEngine(); startForeground(NOTIFICATION_ID, buildNotification(getString(R.string.k2go_dash_rebuilding))); acquireHardwareLocks(); + // ADFA-5011: tag posts as REBUILD so SetupProgressActivity treats this as a blocking rebuild + // session (stays on the animation, no premature "nothing to do → redirect"). + InstallProgressRepository.get().beginRebuild(); InstallProgressRepository.get().postProvisioning(getString(R.string.k2go_dash_rebuilding)); new Thread(this::runDashboardRebuild, "dash-rebuild-service").start(); return START_NOT_STICKY; diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallState.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallState.java index c27f913a..e910ba82 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallState.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallState.java @@ -17,8 +17,10 @@ public final class InstallState { public enum Phase { IDLE, DOWNLOADING, EXTRACTING, PROVISIONING, SUCCESS, FAILED } - /** Which long-running operation this state belongs to (ADFA-4476). */ - public enum Op { INSTALL, RESET } + /** Which long-running operation this state belongs to (ADFA-4476). ADFA-5011 adds REBUILD + * (dash-node REST-core rebuild) so the progress screen can tell a rebuild apart from an install + * and stay put (blocking) until it reaches SUCCESS/FAILED. */ + public enum Op { INSTALL, RESET, REBUILD } public final Phase phase; public final Op op; diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardDetailFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardDetailFragment.java new file mode 100644 index 00000000..5ac09f10 --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardDetailFragment.java @@ -0,0 +1,111 @@ +/* + * ============================================================================ + * Name : DashboardDetailFragment.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5011. Detail card for the dash-node REST core, mirroring the module detail + * (Play Store style): 16:9 image, title + subtitle, meta chips (live version / REST API + * / Runs offline / System core), a description of what the dashboard IS, a "What it + * includes" block and the license. The dashboard is core (not installable/removable), + * so the single action is "Rebuild" — routed through the shared DashboardRebuild gate. + * ============================================================================ + */ +package org.iiab.controller.redesign; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import org.iiab.controller.R; +import org.iiab.controller.util.AppExecutors; + +public class DashboardDetailFragment extends Fragment { + + private final Handler main = new Handler(Looper.getMainLooper()); + private ViewGroup chips; // FlowLayout in XML — typed as ViewGroup so it wraps chips to 2 lines + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle s) { + View root = inflater.inflate(R.layout.fragment_k2go_module_detail, container, false); + + TextView back = root.findViewById(R.id.k2go_moddet_back); + back.setText("‹ " + getString(R.string.k2go_mod_back)); + back.setOnClickListener(v -> requireActivity().getOnBackPressedDispatcher().onBackPressed()); + + ((ImageView) root.findViewById(R.id.k2go_moddet_image)).setImageResource(R.drawable.k2go_module_placeholder); + ((TextView) root.findViewById(R.id.k2go_moddet_title)).setText(R.string.k2go_dash_detail_title); + ((TextView) root.findViewById(R.id.k2go_moddet_sub)).setText(R.string.k2go_dash_detail_sub); + ((TextView) root.findViewById(R.id.k2go_moddet_desc)).setText(R.string.k2go_dash_detail_desc); + + // Meta chips: version (filled in live), then the fixed system-core descriptors. + chips = root.findViewById(R.id.k2go_moddet_chips); + chips.addView(chip(getString(R.string.k2go_dash_chip_rest), R.color.k2go_teal)); + chips.addView(chip(getString(R.string.k2go_mod_runs_offline), R.color.k2go_leaf)); + chips.addView(chip(getString(R.string.k2go_dash_chip_core), R.color.k2go_teal)); + fetchVersionChip(); + + ((TextView) root.findViewById(R.id.k2go_moddet_includes_body)).setText(R.string.k2go_dash_includes); + ((TextView) root.findViewById(R.id.k2go_moddet_license)) + .setText(getString(R.string.k2go_mod_license_fmt, getString(R.string.k2go_dash_license))); + + // Core service — the only action is Rebuild (no schedule/install). Reuse the primary button as + // "Rebuild"; hide the secondary "Install now". + Button rebuild = root.findViewById(R.id.k2go_moddet_schedule); + rebuild.setText(R.string.k2go_dash_rebuild); + rebuild.setOnClickListener(v -> DashboardRebuild.confirmAndStart(this, root)); + root.findViewById(R.id.k2go_moddet_install_now).setVisibility(View.GONE); + + return root; + } + + /** Read the installed version from the rootfs package.json on disk (authoritative, always present; + * no network/proot) and, if found, prepend a "v" chip. */ + private void fetchVersionChip() { + final Context ctx = requireContext().getApplicationContext(); + AppExecutors.get().io().execute(() -> { + final String ver = DashboardVersion.installed(ctx); + main.post(() -> { + if (isAdded() && chips != null && ver != null) { + chips.addView(chip("v" + ver, R.color.k2go_teal), 0); + } + }); + }); + } + + /** Small outlined pill for the meta-chip row (matches ModuleDetailFragment). */ + private TextView chip(String text, int colorRes) { + float d = getResources().getDisplayMetrics().density; + TextView t = new TextView(requireContext()); + t.setText(text); + t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium); + int color = ContextCompat.getColor(requireContext(), colorRes); + t.setTextColor(color); + android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable(); + bg.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE); + bg.setColor(android.graphics.Color.TRANSPARENT); + bg.setCornerRadius(11 * d); + bg.setStroke(Math.max(1, Math.round(1.4f * d)), color); + t.setBackground(bg); + int hp = Math.round(10 * d), vp = Math.round(5 * d); + t.setPadding(hp, vp, hp, vp); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + lp.rightMargin = Math.round(8 * d); + t.setLayoutParams(lp); + return t; + } +} diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuild.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuild.java new file mode 100644 index 00000000..272d7aeb --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuild.java @@ -0,0 +1,74 @@ +/* + * ============================================================================ + * Name : DashboardRebuild.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5011. Single entry point for starting a dash-node REST-core rebuild, shared by + * the Module-management hub row and the dashboard detail card so both offer the exact + * same gated flow: busy check (EnvironmentLock) -> internet check -> confirm dialog -> + * start the guarded InstallService rebuild + open the progress screen (which now stays + * put until the rebuild reaches SUCCESS/FAILED). + * ============================================================================ + */ +package org.iiab.controller.redesign; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.iiab.controller.R; +import org.iiab.controller.install.presentation.InstallService; +import org.iiab.controller.util.Snackbars; + +public final class DashboardRebuild { + private DashboardRebuild() {} + + /** Gate then confirm then start. {@code anchor} is where a "busy"/"no internet" snackbar shows. */ + public static void confirmAndStart(@NonNull Fragment host, @NonNull View anchor) { + Context ctx = host.requireContext(); + if (org.iiab.controller.env.EnvironmentLock.isHeld(ctx)) { + Snackbars.make(anchor, R.string.k2go_install_busy).show(); + return; + } + if (!hasInternet(ctx)) { + Snackbars.make(anchor, R.string.k2go_dash_needs_internet).show(); + return; + } + new MaterialAlertDialogBuilder(ctx) + .setTitle(R.string.k2go_dash_rebuild_confirm_title) + .setMessage(R.string.k2go_dash_rebuild_confirm_msg) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.k2go_dash_rebuild, (d, w) -> start(host)) + .show(); + } + + /** Kick the foreground rebuild service and open the guarded progress screen (flagged as a rebuild + * so it stays on the animation and blocks leaving until the rebuild finishes). */ + private static void start(@NonNull Fragment host) { + Context ctx = host.requireContext(); + Intent svc = new Intent(ctx, InstallService.class) + .setAction(InstallService.ACTION_REBUILD_DASHBOARD); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ctx.startForegroundService(svc); + else ctx.startService(svc); + host.startActivity(new Intent(ctx, SetupProgressActivity.class) + .putExtra(SetupProgressActivity.EXTRA_REBUILD, true)); + } + + /** True when the device reports an internet-capable active network. Unknown -> true (let the + * preflight decide), matching the previous inline check in ModuleHubFragment. */ + public static boolean hasInternet(@NonNull Context ctx) { + android.net.ConnectivityManager cm = (android.net.ConnectivityManager) + ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return true; + android.net.Network n = cm.getActiveNetwork(); + if (n == null) return false; + android.net.NetworkCapabilities caps = cm.getNetworkCapabilities(n); + return caps != null && caps.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET); + } +} diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java index 50d572a7..a8e186f9 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java @@ -6,7 +6,8 @@ * Description : ADFA-5011. App-orchestrated rebuild of the dash-node REST core (Track A of ADR-5011). * The app is the surgeon and the box is the asleep patient: because * PRootEngine#executeInContainer launches a proot, we own the rootfs exclusively, so — - * like a module runrole — we STOP the box services first, then work, then START them. + * like a module runrole — we STOP the box services first, then work, and leave the box + * stopped for the INDEX to boot persistently afterwards (see "Who starts the box" below). * * Bootstrap without shipping code in the APK: the newest scripts are pulled straight * from the on-device clone's remote via `git fetch` + `git show origin/:tools/…` @@ -18,7 +19,16 @@ * -> git fetch + extract preflight/rebuild/smoke to /tmp/k2go + run preflight * -> (only if preflight OK) run rebuild-dashboard.sh (git reset --hard -> build -> * staged smoke test -> back up live dist -> atomic swap -> verify -> rollback) - * -> pdsm start + * -> leave the box STOPPED (the INDEX boots it persistently — see below) + * + * Who starts the box back up: NOT this runner. Every proot here is transient with + * --kill-on-exit, so a service-side `pdsm start` would start services and then kill them + * the instant the proot exits (the "dead Home" bug). Instead, exactly like a proot MODULE + * install, the install INDEX (SetupProgressActivity) is the actuator: after we finish it + * calls ServerController.startEnvironment() ('pdsm start && tail -f /dev/null'), a PERSISTENT + * process-scoped proot that keeps the services alive, then waits for the REST core to answer + * before redirecting. So we own the rootfs (stop) for the rebuild and hand the boot back to + * the index. * * Preflight is non-destructive and gates the rest: it refuses on a dirty clone (so a * user's local edits are never discarded by the reset), no internet, or low disk. The @@ -94,11 +104,12 @@ private void bootstrapAndPreflight(Callback cb) { if (r.ok && exitCode == 0) { runRebuild(cb); } else { - // Nothing was touched — wake the box back up and report why. - startServices(() -> cb.onError(r.reasonSummary())); + // Nothing was touched. Leave the box stopped — the INDEX brings the + // environment back up persistently (see class note); just report why. + cb.onError(r.reasonSummary()); } } - @Override public void onError(String error) { startServices(() -> cb.onError(error)); } + @Override public void onError(String error) { cb.onError(error); } }); } @@ -111,15 +122,17 @@ private void runRebuild(Callback cb) { new PRootEngine.OutputListener() { @Override public void onOutputLine(String line) { cb.onLog(line); } @Override public void onProcessExit(int exitCode) { - // rebuild.sh already restarts dash-node+nginx; bring the rest of the box back too. - startServices(() -> { if (exitCode == 0) cb.onDone(); else cb.onError("rebuild failed"); }); + // Leave the box STOPPED. rebuild.sh's pdsm restarts run in a transient proot and + // die with --kill-on-exit, so a service-side start would only start-then-kill. The + // INDEX is the actuator that boots the environment persistently after we finish + // (startEnvironment: 'pdsm start && tail -f /dev/null'), exactly like the module flow. + if (exitCode == 0) cb.onDone(); else cb.onError("rebuild failed"); } - @Override public void onError(String error) { startServices(() -> cb.onError(error)); } + @Override public void onError(String error) { cb.onError(error); } }); } private void stopServices(Runnable then) { pdsm("stop", then); } - private void startServices(Runnable then) { pdsm("start", then); } private void pdsm(String action, Runnable then) { engine.executeInContainer(ctx, rootfsDir, SHELL + " '/usr/local/bin/pdsm " + action + "'", diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardVersion.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardVersion.java new file mode 100644 index 00000000..87947e96 --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardVersion.java @@ -0,0 +1,46 @@ +/* + * ============================================================================ + * Name : DashboardVersion.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5011. Reads the INSTALLED dash-node version straight from the rootfs on disk + * (installed-rootfs/iiab/library/dashboard/package.json — where install_iiaboa_dashboard + * places it and where dash-node itself reads it via process.cwd()). This is authoritative + * and always available: no network and no proot, so it works even when the server is + * stopped or the running build predates the /system/version REST endpoint (which only + * ships in newer builds — the reason the card showed no version before a first rebuild). + * ============================================================================ + */ +package org.iiab.controller.redesign; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +public final class DashboardVersion { + private DashboardVersion() {} + + /** Installed dash-node version from the rootfs package.json, or null if not found/parseable. */ + @Nullable + public static String installed(@NonNull Context ctx) { + File pkg = new File(ctx.getFilesDir(), + "rootfs/installed-rootfs/iiab/library/dashboard/package.json"); + if (!pkg.exists()) return null; + StringBuilder sb = new StringBuilder(); + try (BufferedReader r = new BufferedReader(new FileReader(pkg))) { + String line; + while ((line = r.readLine()) != null) sb.append(line); + String v = new JSONObject(sb.toString()).optString("version", ""); + return v.isEmpty() ? null : v; + } catch (Exception e) { + return null; + } + } +} diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleDetailFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleDetailFragment.java index 20c50a02..aff4e8fd 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleDetailFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleDetailFragment.java @@ -65,7 +65,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c ((TextView) root.findViewById(R.id.k2go_moddet_desc)).setText(c.descRes); // ADFA-4958: meta chips (size / version / Runs offline), "What it includes", and license. - LinearLayout chips = root.findViewById(R.id.k2go_moddet_chips); + // ADFA-5011: FlowLayout (typed as ViewGroup) so the chips wrap to a 2nd line instead of clipping. + ViewGroup chips = root.findViewById(R.id.k2go_moddet_chips); int sizeRes = ModuleCards.sizeLabelRes(c.key()); // ADFA-4958: maps uses a curated "200 MB+" floor long bytes = ModuleSizes.bytesFor(requireContext(), c.key()); String sizeText = (sizeRes != 0) ? getString(sizeRes) diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java index 1eef4a29..90cbd64f 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java @@ -15,6 +15,7 @@ */ package org.iiab.controller.redesign; +import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -344,9 +345,18 @@ private void addSystemDashboardCard() { col.addView(sub); row.addView(col, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); + // Tapping the card opens the dashboard detail (what it is + full description); the pill is the + // quick Rebuild action. Both route rebuilds through the shared DashboardRebuild gate. + row.setClickable(true); + row.setOnClickListener(v -> { + if (getActivity() instanceof SetupLibraryActivity) { + ((SetupLibraryActivity) getActivity()).openDashboardDetail(); + } + }); + TextView rebuild = statePill(getString(R.string.k2go_dash_rebuild), R.color.k2go_teal); rebuild.setPadding(px(14), px(6), px(14), px(6)); - rebuild.setOnClickListener(v -> onRebuildClicked()); + rebuild.setOnClickListener(v -> DashboardRebuild.confirmAndStart(this, host)); LinearLayout.LayoutParams tlp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); tlp.leftMargin = px(10); @@ -356,73 +366,19 @@ private void addSystemDashboardCard() { fetchDashVersion(sub); } - private void onRebuildClicked() { - if (org.iiab.controller.env.EnvironmentLock.isHeld(requireContext())) { - Snackbars.make(host, R.string.k2go_install_busy).show(); - return; - } - if (!hasInternet()) { - Snackbars.make(host, R.string.k2go_dash_needs_internet).show(); - return; - } - new com.google.android.material.dialog.MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.k2go_dash_rebuild_confirm_title) - .setMessage(R.string.k2go_dash_rebuild_confirm_msg) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.k2go_dash_rebuild, (d, w) -> startRebuild()) - .show(); - } - - private void startRebuild() { - android.content.Intent svc = new android.content.Intent(requireContext(), - org.iiab.controller.install.presentation.InstallService.class) - .setAction(org.iiab.controller.install.presentation.InstallService.ACTION_REBUILD_DASHBOARD); - ContextCompat.startForegroundService(requireContext(), svc); - // Open the guarded status window (same one installs use): it blocks leaving mid-op and - // returns here on re-entry, driven by InstallProgressRepository. - startActivity(new android.content.Intent(requireContext(), SetupProgressActivity.class)); - } - - /** Best-effort: show the installed dash-node version on the card (GET /system/version). */ + /** Show the installed dash-node version on the card. Read from the rootfs package.json on disk + * (authoritative, always present) rather than the REST endpoint — that endpoint only exists in + * newer builds, so on an older install it 404s and the version silently never appeared. */ private void fetchDashVersion(final TextView sub) { + final Context ctx = requireContext().getApplicationContext(); AppExecutors.get().io().execute(() -> { - String v = null; - HttpURLConnection c = null; - try { - URL u = new URL(BoxEndpoints.API + "/system/version"); - c = (HttpURLConnection) u.openConnection(); - c.setUseCaches(false); - c.setConnectTimeout(1500); - c.setReadTimeout(1500); - if (c.getResponseCode() == 200) { - java.io.BufferedReader r = new java.io.BufferedReader(new java.io.InputStreamReader(c.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = r.readLine()) != null) sb.append(line); - r.close(); - v = new org.json.JSONObject(sb.toString()).optString("version", null); - } - } catch (Exception ignored) { - } finally { - if (c != null) c.disconnect(); - } - final String ver = v; + final String ver = DashboardVersion.installed(ctx); main.post(() -> { - if (isAdded() && ver != null && !ver.isEmpty()) sub.setText(getString(R.string.k2go_dash_card_sub_fmt, ver)); + if (isAdded() && ver != null) sub.setText(getString(R.string.k2go_dash_card_sub_fmt, ver)); }); }); } - private boolean hasInternet() { - android.net.ConnectivityManager cm = (android.net.ConnectivityManager) - requireContext().getSystemService(android.content.Context.CONNECTIVITY_SERVICE); - if (cm == null) return true; // can't tell → let the preflight decide - android.net.Network n = cm.getActiveNetwork(); - if (n == null) return false; - android.net.NetworkCapabilities caps = cm.getNetworkCapabilities(n); - return caps != null && caps.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET); - } - private void refreshProceed() { if (proceed == null || !isAdded()) return; int n = ModuleWishlist.size(requireContext()); diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java index a86c7c1e..f0096aac 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java @@ -393,6 +393,14 @@ public void openModuleDetail(String yamlBaseKey) { .commit(); } + /** ADFA-5011: open the dash-node REST core's detail (Play Store-style card, Rebuild-only). */ + public void openDashboardDetail() { + getSupportFragmentManager().beginTransaction() + .replace(R.id.k2go_setup_host, new DashboardDetailFragment()) + .addToBackStack("dashboard_detail") + .commit(); + } + /** ADFA-4842: proceed to the install index for the scheduled modules. The modules are already * banked in ModuleWishlist; the index drains them through the proot queue (ModuleProvisioner), * same mechanism as maps. */ diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java index 0c2c0aca..ae5e3a45 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java @@ -34,6 +34,8 @@ import androidx.core.content.ContextCompat; import org.iiab.controller.R; +import org.iiab.controller.install.presentation.InstallProgressRepository; +import org.iiab.controller.install.presentation.InstallState; import org.iiab.controller.install.presentation.ModuleQueueRepository; import org.iiab.controller.install.presentation.ModuleQueueState; import org.iiab.controller.util.Snackbars; @@ -48,6 +50,10 @@ public class SetupProgressActivity extends AppCompatActivity implements org.iiab /** ADFA-4988: hint from a content confirm — open this stream's detail iff it is the only active one. */ public static final String EXTRA_HINT_STREAM = "hintStream"; + /** ADFA-5011: this screen is driving a dash-node REST-core rebuild (not an install/content drain). + * Latched so the screen stays on the animation and blocks leaving until the rebuild is SUCCESS/FAILED. */ + public static final String EXTRA_REBUILD = "rebuild"; + private static final long READY_POLL_MS = 2000L; private static final long REDIRECT_MS = 3000L; // ADFA-4900: if the maps module queue never reports RUNNING/DONE this long after hand-off, treat @@ -74,6 +80,16 @@ public class SetupProgressActivity extends AppCompatActivity implements org.iiab private boolean showingDetail = false; private boolean leaveWarned = false; // ADFA-4919 (2c): captured the first exit-Back once private boolean probing = false; + private boolean rebuildSeen = false; // ADFA-5011: latched once this screen is a rebuild session + private boolean rebuildRunningSeen = false; // ADFA-5011: latched once we've seen THIS rebuild running, + // so a STALE terminal state from a previous rebuild can't trigger a premature done/redirect on entry + // ADFA-5011: after the rebuild build+swap succeeds, WAIT for the REST core to actually answer before + // redirecting — else we land on a dead Home while pdsm-started services are still coming up. We only + // POLL apiReady() here (read-only); the service already did pdsm start, so we never toggle/stop. + private boolean rebuildStartKicked = false; // ADFA-5011: index booted the environment once (actuator) + private boolean rebuildServerUp = false; // REST core answered after the rebuild + private boolean rebuildServerFailed = false; // services didn't answer within the timeout + private long rebuildServerAt = 0L; // elapsedRealtime when the post-success wait began private boolean mapsLaunched = false; // ADFA-4900: maps (proot) stage has been handed to the queue private long mapsLaunchedAt = 0L; // ADFA-4900: elapsedRealtime when maps was handed off private boolean mapsStartFailed = false; // ADFA-4900: queue never started within the timeout @@ -137,6 +153,10 @@ protected void onCreate(@Nullable Bundle s) { // so a proot-only install could finish without the index ever updating to Finish/redirect. ModuleQueueRepository.get().state().observe(this, st -> render()); + // ADFA-5011: observe the rebuild pipeline so its running→terminal transitions re-render (and, + // on SUCCESS, trigger the redirect). Guarded so it only acts while this is a rebuild session. + InstallProgressRepository.get().state().observe(this, st -> { if (rebuildInSession()) render(); }); + // ADFA-4987: a download notification tapped -> force that stream's detail (not the legacy UI). // ADFA-4988: a content confirm hints its stream -> open its detail only when it is the sole // active stream, otherwise show the index. @@ -188,6 +208,25 @@ protected void onPause() { @Override public void onBackPressed() { if (showingDetail) { backToIndex(); return; } + // ADFA-5011: a rebuild owns the rootfs and can't be abandoned mid-run — same gate as proot. Block + // while building AND through the post-success wait for services to come up (so we never drop the + // user onto a Library showing a half-rebuilt / not-yet-started server). First Back reassures; a + // second backgrounds the app (the rebuild keeps going and reopening resumes here). + if (rebuildInSession()) { + InstallState rst = InstallProgressRepository.get().current(); + boolean rebuiltOk = rebuildRunningSeen && rst.phase == InstallState.Phase.SUCCESS; + boolean stillWorking = InstallProgressRepository.get().isRunning() + || (rebuiltOk && !rebuildServerUp && !rebuildServerFailed); + if (stillWorking) { + if (!leaveWarned) { + leaveWarned = true; + Snackbars.make(findViewById(android.R.id.content), R.string.k2go_setup_leave_hint).show(); + } else { + moveTaskToBack(true); + } + return; + } + } // ADFA-4919 (2c): the index is the LAST barrier for a proot install (runs on the live system, // can't be abandoned mid-run). No up-front confirm (that would spoil the friendly flow). The // FIRST Back reassures via a snackbar; every Back after that sends the whole app to the @@ -253,6 +292,18 @@ private boolean moduleInSession() { return moduleSeen; } + /** ADFA-5011: is a dash-node rebuild the operation driving THIS screen? Latched from the launch + * extra (primary signal) or a LIVE REBUILD op in InstallProgressRepository (covers a reopen while + * the rebuild runs; a stale terminal REBUILD is excluded by the isRunning() check). Once latched it + * stays for the screen's life so the terminal result (done/failed) is shown, not skipped. */ + private boolean rebuildInSession() { + if (rebuildSeen) return true; + if (getIntent() != null && getIntent().getBooleanExtra(EXTRA_REBUILD, false)) { rebuildSeen = true; return true; } + if (InstallProgressRepository.get().currentOp() == InstallState.Op.REBUILD + && InstallProgressRepository.get().isRunning()) { rebuildSeen = true; return true; } + return false; + } + // ---- readiness gate + serialized install pipeline (ADFA-4900) ---- // Once the REST engine is up, run the install tasks as an ORDERED, serialized pipeline: // maps (proot / runrole) exclusively first, then ZIM, then Books, auto-continuing between @@ -262,6 +313,45 @@ private boolean moduleInSession() { @Override public void run() { if (probing) return; if (isFinishing()) return; + // ADFA-5011: a dashboard rebuild owns the rootfs (the service does pdsm stop → build → swap and + // leaves the box STOPPED). Skip the normal install readiness/orchestrate path entirely — it has + // nothing to drain and would see the server still up in the first seconds, declare "nothing to + // do" and redirect (the original bug). Instead: re-render from the rebuild state; and once the + // rebuild is terminal, the INDEX boots the environment persistently and waits for it (below). + if (rebuildInSession()) { + InstallState cur = InstallProgressRepository.get().current(); + boolean rebuiltOk = rebuildRunningSeen && cur.phase == InstallState.Phase.SUCCESS; + boolean rebuiltFail = rebuildRunningSeen && cur.phase == InstallState.Phase.FAILED; + // Rebuild done → the INDEX is the actuator that boots the environment PERSISTENTLY + // (startEnvironment = 'pdsm start && tail -f /dev/null'), exactly like the module flow. + // The rebuild service left the box stopped (its transient proots would kill any service + // they started via --kill-on-exit), so nothing else brings it up. Kick it exactly once. + if ((rebuiltOk || rebuiltFail) && !rebuildStartKicked) { + rebuildStartKicked = true; + rebuildServerAt = SystemClock.elapsedRealtime(); + serverController.startEnvironment(); + } + // On success, probe the REST core (read-only) until it answers or the wait times out — + // so we redirect only once services are truly up, never onto a dead Home. + if (rebuiltOk && !rebuildServerUp && !rebuildServerFailed && !probing) { + probing = true; + AppExecutors.get().io().execute(() -> { + final boolean up = RestReadiness.apiReady(); + main.post(() -> { + probing = false; + if (isFinishing()) return; + if (up) rebuildServerUp = true; + else if (SystemClock.elapsedRealtime() - rebuildServerAt > SERVER_UP_TIMEOUT_MS) rebuildServerFailed = true; + render(); + }); + }); + } + render(); // sets rebuildRunningSeen once the running state is observed + boolean settled = rebuiltFail + || (rebuiltOk && (rebuildServerUp || rebuildServerFailed)); + if (!settled) main.postDelayed(readyPoll, READY_POLL_MS); + return; + } // ADFA-4842: a MODULE (solo-proot) install stops the server and runs its OWN proot — there is // no REST engine to wait for, and we must NEVER try to "start services" (a second proot) mid- // runrole. Skip the REST readiness gate entirely: the runrole queue drives progress, and the @@ -353,6 +443,10 @@ private boolean orchestrateStep() { private void render() { if (sections == null || showingDetail) return; + // ADFA-5011: a rebuild has its own, simpler surface (one row + status), driven by + // InstallProgressRepository — never the install/content completion logic below. + if (rebuildInSession()) { renderRebuild(); return; } + boolean mapsShown = mapsInSession(); // ADFA-4900 / ADFA-4919 (durable across index instances) boolean moduleShown = moduleInSession(); // ADFA-4842: non-maps proot module batch boolean zimShown = ZimDownloadService.hasSession() || ZimWishlist.size(this) > 0; @@ -462,6 +556,103 @@ private void render() { } } + /** ADFA-5011: dedicated render for a dashboard rebuild — one row + a status line driven by + * InstallProgressRepository. While running the screen is the gate (no Run in background, Back is + * softened then backgrounds the app); on SUCCESS it redirects to a live Library; on FAILED it + * shows Finish + the note (never a silent success on a half-rebuilt server). */ + private void renderRebuild() { + InstallState st = InstallProgressRepository.get().current(); + if (st.isRunning()) rebuildRunningSeen = true; + // Only honor a terminal state once THIS rebuild has been seen running — otherwise a stale + // SUCCESS/FAILED from a previous rebuild would flash on entry and trigger a premature redirect. + boolean rebuiltOk = rebuildRunningSeen && st.phase == InstallState.Phase.SUCCESS; + boolean rebuildFailed = rebuildRunningSeen && st.phase == InstallState.Phase.FAILED; + + // Note: the post-success wait for the REST core (apiReady poll) is driven by readyPoll, which is + // lifecycle-managed (posted in onResume, cleared in onPause). This method only reflects state. + boolean serverWait = rebuiltOk && !rebuildServerUp && !rebuildServerFailed; // rebuilt, services coming up + boolean done = rebuiltOk && rebuildServerUp; // rebuilt + REST core answered + boolean error = rebuildFailed || (rebuiltOk && rebuildServerFailed); // rebuild failed, or services never came up + boolean working = !done && !error; // building OR waiting for services + + String sub; + if (error) sub = rebuildFailed + ? ((st.message != null && !st.message.isEmpty()) ? st.message : getString(R.string.k2go_dash_rebuild_failed)) + : getString(R.string.k2go_dash_services_failed); + else if (done) sub = getString(R.string.k2go_setup_state_done); + else if (serverWait) sub = getString(R.string.k2go_setup_starting); + else sub = getString(R.string.k2go_dash_rebuild_building); + + sections.removeAllViews(); + sections.addView(rebuildRow(rebuiltOk && !error, error, sub)); // check once the build succeeded; alert on error + + if (contextText != null) contextText.setText(R.string.k2go_setup_context_proot); + + tint(dot, done ? R.color.k2go_leaf : R.color.k2go_amber); + if (working) { + statusEllipsis.start(getString(serverWait ? R.string.k2go_setup_starting : R.string.k2go_dash_rebuilding)); + } else { + statusEllipsis.stop(); + statusText.setText(done ? R.string.k2go_setup_state_done + : (rebuildFailed ? R.string.k2go_dash_rebuild_failed : R.string.k2go_dash_services_failed)); + } + + if (done && !redirectCancelled) { + show(redirect, true); show(cancel, true); + show(finishBtn, false); show(finishNote, false); show(runBgBtn, false); + scheduleRedirect(); + } else if (done) { // cancelled by the user — stay, reveal Finish + cancelRedirect(); + show(finishBtn, true); show(runBgBtn, false); + show(redirect, false); show(cancel, false); show(finishNote, false); + } else if (error) { + cancelRedirect(); + show(finishBtn, true); show(finishNote, true); show(runBgBtn, false); + show(redirect, false); show(cancel, false); + } else { // building or waiting for services — the screen is the gate; no leaving. + cancelRedirect(); + show(runBgBtn, false); show(finishBtn, false); show(finishNote, false); + show(redirect, false); show(cancel, false); + } + } + + /** ADFA-5011: the single dashboard-rebuild row: spinner while working → check (build done) / amber + * alert (failed), with the given status subtitle. */ + private View rebuildRow(boolean check, boolean alert, String subText) { + LinearLayout row = new LinearLayout(this); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setGravity(Gravity.CENTER_VERTICAL); + row.setBackgroundResource(R.drawable.k2go_card_bg); + row.setPadding(px(16), px(14), px(16), px(14)); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + lp.bottomMargin = px(12); + row.setLayoutParams(lp); + + LinearLayout slot = new LinearLayout(this); + slot.setGravity(Gravity.CENTER); + LinearLayout.LayoutParams slotLp = new LinearLayout.LayoutParams(px(24), px(24)); + slotLp.rightMargin = px(10); + slot.addView(indicator(true, check || alert, alert ? 1 : 0)); + row.addView(slot, slotLp); + + LinearLayout col = new LinearLayout(this); + col.setOrientation(LinearLayout.VERTICAL); + TextView h = new TextView(this); + h.setText(R.string.k2go_dash_card_title); + h.setTypeface(h.getTypeface(), android.graphics.Typeface.BOLD); + h.setTextColor(ContextCompat.getColor(this, R.color.k2go_ink)); + h.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_TitleMedium); + col.addView(h); + TextView sub = new TextView(this); + sub.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall); + sub.setText(subText); + sub.setTextColor(ContextCompat.getColor(this, alert ? R.color.k2go_amber_text : R.color.k2go_muted)); + col.addView(sub); + row.addView(col, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); + return row; + } + private static int failedCount(int[] status, int failedVal) { if (status == null) return 0; int n = 0; for (int st : status) if (st == failedVal) n++; return n; diff --git a/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml b/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml index 917ed0b1..d0ea9234 100644 --- a/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml +++ b/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml @@ -57,12 +57,13 @@ android:textAppearance="?attr/textAppearanceBodyMedium" android:textColor="@color/k2go_muted" /> - + + android:layout_marginTop="12dp" /> Rebuild the REST core? Updates dash-node to the latest version. The server is briefly unavailable while it rebuilds; don\'t leave this screen until it finishes. Rebuild needs an internet connection. + + Dashboard (REST API) + System core · always installed + The dashboard is the box\'s REST API core (dash-node) — a small Node service on 127.0.0.1:4000, fronted by nginx and supervised by pdsm. It powers every in-app library action: browsing and downloading Books, adding and deleting Wikipedia and ZIM content, Kolibri sessions, Maps, and system status. Unlike the content modules, it isn\'t something you install or remove — it ships with the box and is always present. When a newer build is available you can rebuild it in place. + Books, Wikipedia/ZIM, Kolibri and Maps REST endpoints; the nginx vhost; and system version + rebuild endpoints — all served on 127.0.0.1:4000. + REST API + System core + + GPL-2.0 + + Building, testing and swapping in place… + Rebuild failed + Rebuilt, but services didn\'t come up in time Manage downloads Get more content From ea438644f1ad7fb1e216cbf11d984af0fcc48699 Mon Sep 17 00:00:00 2001 From: Luis-ADFA Date: Tue, 4 Aug 2026 10:38:06 -0600 Subject: [PATCH 3/4] ADFA-5011 feat(dashboard): set main as target branch, apply code review fixes and update string --- .../redesign/DashboardRebuildRunner.java | 2 +- .../redesign/SetupProgressActivity.java | 16 +++++++++++----- .../app/src/main/res/values/strings_k2go.xml | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java index a8e186f9..18b1f13b 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/DashboardRebuildRunner.java @@ -54,7 +54,7 @@ public final class DashboardRebuildRunner { private static final String SHELL = "/usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin bash -lc"; private static final String CLONE = "/opt/iiab-android"; - private static final String BRANCH = "feat/ADFA-5011-dashboard-rebuild"; // TODO(ADFA-5011): override for pre-merge testing + private static final String BRANCH = "main"; // the rebuild always tracks the mainline dashboard private static final String TMP = "/tmp/k2go"; // where we drop the newest scripts to run them public interface Callback { diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java index ae5e3a45..061261ac 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/SetupProgressActivity.java @@ -331,9 +331,13 @@ private boolean rebuildInSession() { rebuildServerAt = SystemClock.elapsedRealtime(); serverController.startEnvironment(); } + render(); // sets rebuildRunningSeen once the running state is observed // On success, probe the REST core (read-only) until it answers or the wait times out — - // so we redirect only once services are truly up, never onto a dead Home. - if (rebuiltOk && !rebuildServerUp && !rebuildServerFailed && !probing) { + // so we redirect only once services are truly up, never onto a dead Home. Reschedule from + // INSIDE the probe callback (not below): apiReady() can block up to ~5s while the server + // boots — longer than READY_POLL_MS — and the top `if (probing) return` would otherwise + // strand the loop if we also scheduled here. + if (rebuiltOk && !rebuildServerUp && !rebuildServerFailed) { probing = true; AppExecutors.get().io().execute(() -> { final boolean up = RestReadiness.apiReady(); @@ -343,12 +347,13 @@ private boolean rebuildInSession() { if (up) rebuildServerUp = true; else if (SystemClock.elapsedRealtime() - rebuildServerAt > SERVER_UP_TIMEOUT_MS) rebuildServerFailed = true; render(); + if (!rebuildServerUp && !rebuildServerFailed) main.postDelayed(readyPoll, READY_POLL_MS); }); }); + return; } - render(); // sets rebuildRunningSeen once the running state is observed - boolean settled = rebuiltFail - || (rebuiltOk && (rebuildServerUp || rebuildServerFailed)); + // Building, or terminal-and-settled. Keep polling only while still building. + boolean settled = rebuiltFail || (rebuiltOk && (rebuildServerUp || rebuildServerFailed)); if (!settled) main.postDelayed(readyPoll, READY_POLL_MS); return; } @@ -598,6 +603,7 @@ private void renderRebuild() { } if (done && !redirectCancelled) { + redirect.setText(R.string.k2go_dash_redirect); // rebuild-specific wording (not "Installation complete") show(redirect, true); show(cancel, true); show(finishBtn, false); show(finishNote, false); show(runBgBtn, false); scheduleRedirect(); diff --git a/controller/app/src/main/res/values/strings_k2go.xml b/controller/app/src/main/res/values/strings_k2go.xml index afb01216..46ca46f7 100644 --- a/controller/app/src/main/res/values/strings_k2go.xml +++ b/controller/app/src/main/res/values/strings_k2go.xml @@ -732,6 +732,7 @@ GPL-2.0 + Rebuild complete — sending you to your library… Building, testing and swapping in place… Rebuild failed Rebuilt, but services didn\'t come up in time From a18a202cadc74443329e8caa3effde0aab8d6e65 Mon Sep 17 00:00:00 2001 From: Luis-ADFA Date: Tue, 4 Aug 2026 10:58:30 -0600 Subject: [PATCH 4/4] ADFA-5011 chore(l10n): translate dashboard rebuild strings to 33 locales --- .../src/main/res/values-ar/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-az/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-bg/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-bn/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-cs/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-de/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-el/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-es/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-fa/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-fr/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-gu/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-hi/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-hu/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-in/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-it/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-ja/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-ko/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-lt/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-nl/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-no/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-pl/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-pt/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-ro/strings_k2go.xml | 20 +++++++++++++++++++ .../main/res/values-ru-rRU/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-sk/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-sr/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-sw/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-ta/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-tr/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-uk/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-vi/strings_k2go.xml | 20 +++++++++++++++++++ .../src/main/res/values-yo/strings_k2go.xml | 20 +++++++++++++++++++ .../main/res/values-zh-rCN/strings_k2go.xml | 20 +++++++++++++++++++ 33 files changed, 660 insertions(+) diff --git a/controller/app/src/main/res/values-ar/strings_k2go.xml b/controller/app/src/main/res/values-ar/strings_k2go.xml index f36c4890..c5d7073b 100644 --- a/controller/app/src/main/res/values-ar/strings_k2go.xml +++ b/controller/app/src/main/res/values-ar/strings_k2go.xml @@ -673,4 +673,24 @@ تم حذف %1$s تم الحذف — مخفي الآن، وسيُزال بالكامل بعد انتهاء التنزيل الحالي. تعذّر الحذف. يُرجى المحاولة مرة أخرى. + + إعادة بناء لوحة التحكم… + Dashboard (REST API) + نواة REST + نواة REST · مثبَّت v%1$s + إعادة البناء + إعادة بناء نواة REST؟ + تُحدِّث dash-node إلى أحدث إصدار. يكون الخادم غير متاح لفترة وجيزة أثناء إعادة البناء؛ لا تغادر هذه الشاشة حتى تنتهي. + تتطلب إعادة البناء اتصالاً بالإنترنت. + Dashboard (REST API) + نواة النظام · مثبَّتة دائمًا + لوحة التحكم هي نواة واجهة REST API للجهاز (dash-node): خدمة Node صغيرة على 127.0.0.1:4000، يقدّمها nginx ويشرف عليها pdsm. تُشغِّل كل إجراء للمكتبة داخل التطبيق: تصفّح الكتب وتنزيلها، وإضافة محتوى Wikipedia وZIM وحذفه، وجلسات Kolibri، والخرائط، وحالة النظام. على عكس وحدات المحتوى، لا تُثبَّت ولا تُزال — فهي تأتي مع الجهاز وموجودة دائمًا. عند توفّر إصدار أحدث يمكنك إعادة بنائها في مكانها. + نقاط نهاية REST للكتب وWikipedia/ZIM وKolibri والخرائط؛ ومضيف nginx الافتراضي؛ ونقاط نهاية إصدار النظام وإعادة البناء — كلها على 127.0.0.1:4000. + REST API + نواة النظام + GPL-2.0 + اكتملت إعادة البناء — يتم نقلك إلى مكتبتك… + يتم البناء والاختبار والاستبدال في المكان… + فشلت إعادة البناء + تمت إعادة البناء لكن الخدمات لم تبدأ في الوقت المناسب diff --git a/controller/app/src/main/res/values-az/strings_k2go.xml b/controller/app/src/main/res/values-az/strings_k2go.xml index 8010ebaf..6a94e540 100644 --- a/controller/app/src/main/res/values-az/strings_k2go.xml +++ b/controller/app/src/main/res/values-az/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s silindi Silindi — hələlik gizlədilib, cari endirmə bitdikdə tam silinəcək. Silinmədi. Zəhmət olmasa yenidən cəhd edin. + + Panel yenidən qurulur… + Dashboard (REST API) + REST nüvəsi + REST nüvəsi · quraşdırılıb v%1$s + Yenidən qur + REST nüvəsi yenidən qurulsun? + dash-node-u ən son versiyaya yeniləyir. Yenidən qurma zamanı server qısa müddət əlçatmaz olur; bitənə qədər bu ekranı tərk etməyin. + Yenidən qurma internet bağlantısı tələb edir. + Dashboard (REST API) + Sistem nüvəsi · həmişə quraşdırılıb + Panel qutunun REST API nüvəsidir (dash-node): 127.0.0.1:4000 ünvanında işləyən, nginx tərəfindən təqdim edilən və pdsm tərəfindən idarə olunan kiçik Node xidmətidir. O, tətbiqdəki bütün kitabxana əməliyyatlarını işə salır: Kitablara baxış və endirmə, Wikipedia və ZIM məzmununun əlavə edilməsi və silinməsi, Kolibri sessiyaları, Xəritələr və sistem vəziyyəti. Məzmun modullarından fərqli olaraq quraşdırılmır və silinmir — qutu ilə birlikdə gəlir və həmişə mövcuddur. Daha yeni versiya olduqda onu yerində yenidən qura bilərsiniz. + Kitablar, Wikipedia/ZIM, Kolibri və Xəritələr üçün REST son nöqtələri; nginx vhost; və sistem versiyası ilə yenidən qurma son nöqtələri — hamısı 127.0.0.1:4000 ünvanında. + REST API + Sistem nüvəsi + GPL-2.0 + Yenidən qurma tamamlandı — sizi kitabxananıza yönləndiririk… + Yerində qurulur, sınaqdan keçirilir və dəyişdirilir… + Yenidən qurma alınmadı + Yenidən quruldu, lakin xidmətlər vaxtında başlamadı diff --git a/controller/app/src/main/res/values-bg/strings_k2go.xml b/controller/app/src/main/res/values-bg/strings_k2go.xml index b05893fe..0001d774 100644 --- a/controller/app/src/main/res/values-bg/strings_k2go.xml +++ b/controller/app/src/main/res/values-bg/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s е изтрит Изтрито — засега скрито, напълно премахнато след завършване на текущото изтегляне. Изтриването не бе успешно. Опитайте отново. + + Пресъздаване на таблото… + Dashboard (REST API) + REST ядро + REST ядро · инсталирана v%1$s + Пресъздаване + Да се пресъздаде ли REST ядрото? + Актуализира dash-node до най-новата версия. Сървърът е за кратко недостъпен по време на пресъздаването; не напускайте този екран, докато не приключи. + Пресъздаването изисква интернет връзка. + Dashboard (REST API) + Системно ядро · винаги инсталирано + Таблото е REST API ядрото на устройството (dash-node): малка Node услуга на 127.0.0.1:4000, обслужвана от nginx и наблюдавана от pdsm. То захранва всяко действие на библиотеката в приложението: разглеждане и изтегляне на Книги, добавяне и изтриване на съдържание от Wikipedia и ZIM, сесии на Kolibri, Карти и състояние на системата. За разлика от модулите за съдържание, то не се инсталира и не се премахва — идва с устройството и винаги присъства. Когато има по-нова версия, можете да го пресъздадете на място. + REST крайни точки за Книги, Wikipedia/ZIM, Kolibri и Карти; nginx vhost; и крайни точки за системна версия и пресъздаване — всички на 127.0.0.1:4000. + REST API + Системно ядро + GPL-2.0 + Пресъздаването завърши — насочваме ви към библиотеката… + Компилиране, тестване и замяна на място… + Пресъздаването е неуспешно + Пресъздадено, но услугите не се стартираха навреме diff --git a/controller/app/src/main/res/values-bn/strings_k2go.xml b/controller/app/src/main/res/values-bn/strings_k2go.xml index 1f4c05c2..47222305 100644 --- a/controller/app/src/main/res/values-bn/strings_k2go.xml +++ b/controller/app/src/main/res/values-bn/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s মুছে ফেলা হয়েছে মুছে ফেলা হয়েছে — আপাতত লুকানো, বর্তমান ডাউনলোড শেষ হলে সম্পূর্ণ সরিয়ে ফেলা হবে। মুছে ফেলা যায়নি। আবার চেষ্টা করুন। + + ড্যাশবোর্ড পুনর্নির্মাণ হচ্ছে… + Dashboard (REST API) + REST কোর + REST কোর · ইনস্টল করা v%1$s + পুনর্নির্মাণ + REST কোর পুনর্নির্মাণ করবেন? + dash-node কে সর্বশেষ সংস্করণে আপডেট করে। পুনর্নির্মাণের সময় সার্ভার কিছুক্ষণের জন্য অনুপলব্ধ থাকে; শেষ না হওয়া পর্যন্ত এই স্ক্রিন ছেড়ে যাবেন না। + পুনর্নির্মাণের জন্য ইন্টারনেট সংযোগ প্রয়োজন। + Dashboard (REST API) + সিস্টেম কোর · সর্বদা ইনস্টল করা + ড্যাশবোর্ড হলো বাক্সের REST API কোর (dash-node): 127.0.0.1:4000-এ একটি ছোট Node পরিষেবা, যা nginx পরিবেশন করে এবং pdsm তত্ত্বাবধান করে। এটি অ্যাপে প্রতিটি লাইব্রেরি ক্রিয়া চালায়: বই ব্রাউজ ও ডাউনলোড, Wikipedia ও ZIM কনটেন্ট যোগ ও মুছে ফেলা, Kolibri সেশন, মানচিত্র এবং সিস্টেম অবস্থা। কনটেন্ট মডিউলের বিপরীতে, এটি ইনস্টল বা সরানো হয় না — এটি বাক্সের সাথে আসে এবং সবসময় উপস্থিত থাকে। নতুন সংস্করণ পাওয়া গেলে আপনি এটি জায়গাতেই পুনর্নির্মাণ করতে পারেন। + বই, Wikipedia/ZIM, Kolibri এবং মানচিত্রের REST এন্ডপয়েন্ট; nginx vhost; এবং সিস্টেম সংস্করণ ও পুনর্নির্মাণ এন্ডপয়েন্ট — সবই 127.0.0.1:4000-এ। + REST API + সিস্টেম কোর + GPL-2.0 + পুনর্নির্মাণ সম্পন্ন — আপনার লাইব্রেরিতে নিয়ে যাওয়া হচ্ছে… + জায়গাতেই তৈরি, পরীক্ষা ও প্রতিস্থাপন হচ্ছে… + পুনর্নির্মাণ ব্যর্থ + পুনর্নির্মিত, কিন্তু পরিষেবাগুলি সময়মতো চালু হয়নি diff --git a/controller/app/src/main/res/values-cs/strings_k2go.xml b/controller/app/src/main/res/values-cs/strings_k2go.xml index 02726725..e14cccda 100644 --- a/controller/app/src/main/res/values-cs/strings_k2go.xml +++ b/controller/app/src/main/res/values-cs/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s odstraněno Odstraněno — prozatím skryto, zcela odstraněno po dokončení aktuálního stahování. Odstranění se nezdařilo. Zkuste to znovu. + + Přestavba nástěnky… + Dashboard (REST API) + Jádro REST + Jádro REST · nainstalováno v%1$s + Přestavět + Přestavět jádro REST? + Aktualizuje dash-node na nejnovější verzi. Server je během přestavby krátce nedostupný; neopouštějte tuto obrazovku, dokud neskončí. + Přestavba vyžaduje připojení k internetu. + Dashboard (REST API) + Jádro systému · vždy nainstalováno + Nástěnka je jádro REST API zařízení (dash-node) — malá služba Node na 127.0.0.1:4000, obsluhovaná nginx a řízená pdsm. Pohání každou akci knihovny v aplikaci: procházení a stahování Knih, přidávání a mazání obsahu Wikipedie a ZIM, relace Kolibri, Mapy a stav systému. Na rozdíl od modulů obsahu se neinstaluje ani neodstraňuje — je součástí zařízení a je vždy přítomna. Když je k dispozici novější verze, můžete ji přestavět na místě. + REST koncové body Knih, Wikipedie/ZIM, Kolibri a Map; nginx vhost; a koncové body verze systému a přestavby — vše na 127.0.0.1:4000. + REST API + Jádro systému + GPL-2.0 + Přestavba dokončena — přesměrováváme vás do knihovny… + Sestavování, testování a výměna na místě… + Přestavba selhala + Přestavěno, ale služby se nespustily včas diff --git a/controller/app/src/main/res/values-de/strings_k2go.xml b/controller/app/src/main/res/values-de/strings_k2go.xml index 52d64666..0443cca4 100644 --- a/controller/app/src/main/res/values-de/strings_k2go.xml +++ b/controller/app/src/main/res/values-de/strings_k2go.xml @@ -674,4 +674,24 @@ %1$s gelöscht Gelöscht – vorerst ausgeblendet, vollständig entfernt, sobald der aktuelle Download abgeschlossen ist. Löschen fehlgeschlagen. Bitte versuche es erneut. + + Dashboard wird neu erstellt… + Dashboard (REST API) + REST-Kern + REST-Kern · installiert v%1$s + Neu erstellen + REST-Kern neu erstellen? + Aktualisiert dash-node auf die neueste Version. Der Server ist während der Neuerstellung kurz nicht verfügbar; verlassen Sie diesen Bildschirm nicht, bis es fertig ist. + Für die Neuerstellung ist eine Internetverbindung nötig. + Dashboard (REST API) + Systemkern · immer installiert + Das Dashboard ist der REST-API-Kern der Box (dash-node) — ein kleiner Node-Dienst auf 127.0.0.1:4000, bereitgestellt über nginx und überwacht von pdsm. Er treibt jede Bibliotheksaktion in der App an: Bücher durchsuchen und herunterladen, Wikipedia- und ZIM-Inhalte hinzufügen und löschen, Kolibri-Sitzungen, Karten und Systemstatus. Anders als die Inhaltsmodule wird er nicht installiert oder entfernt — er gehört zur Box und ist immer vorhanden. Wenn eine neuere Version verfügbar ist, können Sie ihn an Ort und Stelle neu erstellen. + REST-Endpunkte für Bücher, Wikipedia/ZIM, Kolibri und Karten; der nginx-vhost; sowie Endpunkte für Systemversion und Neuerstellung — alle unter 127.0.0.1:4000. + REST API + Systemkern + GPL-2.0 + Neuerstellung abgeschlossen — weiter zu Ihrer Bibliothek… + Erstellen, Testen und Austauschen vor Ort… + Neuerstellung fehlgeschlagen + Neu erstellt, aber die Dienste wurden nicht rechtzeitig gestartet diff --git a/controller/app/src/main/res/values-el/strings_k2go.xml b/controller/app/src/main/res/values-el/strings_k2go.xml index 3a8eb3bc..e9fc7f9e 100644 --- a/controller/app/src/main/res/values-el/strings_k2go.xml +++ b/controller/app/src/main/res/values-el/strings_k2go.xml @@ -674,4 +674,24 @@ Διαγράφηκε %1$s Διαγράφηκε — προς το παρόν κρυφό, θα αφαιρεθεί πλήρως μόλις ολοκληρωθεί η τρέχουσα λήψη. Η διαγραφή απέτυχε. Δοκιμάστε ξανά. + + Ανασυγκρότηση του πίνακα… + Dashboard (REST API) + Πυρήνας REST + Πυρήνας REST · εγκατεστημένη v%1$s + Ανασυγκρότηση + Ανασυγκρότηση του πυρήνα REST; + Ενημερώνει το dash-node στην πιο πρόσφατη έκδοση. Ο διακομιστής είναι για λίγο μη διαθέσιμος κατά την ανασυγκρότηση· μην φύγετε από αυτήν την οθόνη μέχρι να ολοκληρωθεί. + Η ανασυγκρότηση χρειάζεται σύνδεση στο διαδίκτυο. + Dashboard (REST API) + Πυρήνας συστήματος · πάντα εγκατεστημένος + Ο πίνακας είναι ο πυρήνας REST API της συσκευής (dash-node): μια μικρή υπηρεσία Node στο 127.0.0.1:4000, που εξυπηρετείται από το nginx και επιβλέπεται από το pdsm. Τροφοδοτεί κάθε ενέργεια της βιβλιοθήκης στην εφαρμογή: περιήγηση και λήψη Βιβλίων, προσθήκη και διαγραφή περιεχομένου Wikipedia και ZIM, συνεδρίες Kolibri, Χάρτες και κατάσταση συστήματος. Σε αντίθεση με τις μονάδες περιεχομένου, δεν εγκαθίσταται ούτε αφαιρείται — συνοδεύει τη συσκευή και είναι πάντα παρών. Όταν υπάρχει νεότερη έκδοση, μπορείτε να τον ανασυγκροτήσετε επιτόπου. + Σημεία REST για Βιβλία, Wikipedia/ZIM, Kolibri και Χάρτες· το vhost του nginx· και σημεία έκδοσης συστήματος και ανασυγκρότησης — όλα στο 127.0.0.1:4000. + REST API + Πυρήνας συστήματος + GPL-2.0 + Η ανασυγκρότηση ολοκληρώθηκε — μετάβαση στη βιβλιοθήκη σας… + Κατασκευή, δοκιμή και επιτόπια αντικατάσταση… + Η ανασυγκρότηση απέτυχε + Ανασυγκροτήθηκε, αλλά οι υπηρεσίες δεν ξεκίνησαν εγκαίρως diff --git a/controller/app/src/main/res/values-es/strings_k2go.xml b/controller/app/src/main/res/values-es/strings_k2go.xml index 32537127..85d94651 100644 --- a/controller/app/src/main/res/values-es/strings_k2go.xml +++ b/controller/app/src/main/res/values-es/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s eliminado Eliminado — oculto por ahora, se quitará por completo cuando termine la descarga actual. No se pudo eliminar. Inténtalo de nuevo. + + Reconstruyendo el dashboard… + Dashboard (REST API) + Núcleo REST + Núcleo REST · instalado v%1$s + Reconstruir + ¿Reconstruir el núcleo REST? + Actualiza dash-node a la última versión. El servidor queda brevemente no disponible mientras se reconstruye; no salgas de esta pantalla hasta que termine. + La reconstrucción necesita conexión a internet. + Dashboard (REST API) + Núcleo del sistema · siempre instalado + El dashboard es el núcleo de la API REST de la caja (dash-node): un pequeño servicio Node en 127.0.0.1:4000, servido por nginx y supervisado por pdsm. Impulsa cada acción de la biblioteca en la app: explorar y descargar Libros, añadir y eliminar contenido de Wikipedia y ZIM, sesiones de Kolibri, Mapas y el estado del sistema. A diferencia de los módulos de contenido, no es algo que instales o quites: viene con la caja y siempre está presente. Cuando hay una versión más reciente, puedes reconstruirlo en el sitio. + Endpoints REST de Libros, Wikipedia/ZIM, Kolibri y Mapas; el vhost de nginx; y los endpoints de versión y reconstrucción del sistema, todo servido en 127.0.0.1:4000. + REST API + Núcleo del sistema + GPL-2.0 + Reconstrucción completa — te llevamos a tu biblioteca… + Compilando, probando y sustituyendo en el sitio… + Falló la reconstrucción + Reconstruido, pero los servicios no arrancaron a tiempo diff --git a/controller/app/src/main/res/values-fa/strings_k2go.xml b/controller/app/src/main/res/values-fa/strings_k2go.xml index f7222e9e..98d81387 100644 --- a/controller/app/src/main/res/values-fa/strings_k2go.xml +++ b/controller/app/src/main/res/values-fa/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s حذف شد حذف شد — فعلاً پنهان شده، پس از پایان دانلود فعلی به‌طور کامل حذف می‌شود. حذف ممکن نشد. لطفاً دوباره تلاش کنید. + + در حال بازسازی داشبورد… + Dashboard (REST API) + هستهٔ REST + هستهٔ REST · نصب‌شده v%1$s + بازسازی + هستهٔ REST بازسازی شود؟ + dash-node را به آخرین نسخه به‌روزرسانی می‌کند. سرور در حین بازسازی برای مدت کوتاهی در دسترس نیست؛ تا پایان کار این صفحه را ترک نکنید. + بازسازی به اتصال اینترنت نیاز دارد. + Dashboard (REST API) + هستهٔ سیستم · همیشه نصب‌شده + داشبورد هستهٔ REST API دستگاه است (dash-node): یک سرویس کوچک Node روی 127.0.0.1:4000 که nginx آن را ارائه می‌دهد و pdsm بر آن نظارت دارد. هر کنش کتابخانه در برنامه را پیش می‌برد: مرور و دانلود کتاب‌ها، افزودن و حذف محتوای Wikipedia و ZIM، نشست‌های Kolibri، نقشه‌ها و وضعیت سیستم. برخلاف ماژول‌های محتوا، نصب یا حذف نمی‌شود — همراه دستگاه می‌آید و همیشه حاضر است. وقتی نسخهٔ جدیدتری در دسترس باشد می‌توانید آن را در همان‌جا بازسازی کنید. + نقاط پایانی REST برای کتاب‌ها، Wikipedia/ZIM، Kolibri و نقشه‌ها؛ vhost مربوط به nginx؛ و نقاط پایانی نسخهٔ سیستم و بازسازی — همه روی 127.0.0.1:4000. + REST API + هستهٔ سیستم + GPL-2.0 + بازسازی کامل شد — در حال انتقال به کتابخانهٔ شما… + در حال ساخت، آزمایش و جایگزینی در محل… + بازسازی ناموفق بود + بازسازی شد، اما سرویس‌ها به‌موقع راه‌اندازی نشدند diff --git a/controller/app/src/main/res/values-fr/strings_k2go.xml b/controller/app/src/main/res/values-fr/strings_k2go.xml index a9712d11..7717255f 100644 --- a/controller/app/src/main/res/values-fr/strings_k2go.xml +++ b/controller/app/src/main/res/values-fr/strings_k2go.xml @@ -683,4 +683,24 @@ %1$s supprimé Supprimé — masqué pour l\'instant, entièrement retiré une fois le téléchargement en cours terminé. Échec de la suppression. Veuillez réessayer. + + Reconstruction du dashboard… + Dashboard (REST API) + Cœur REST + Cœur REST · v%1$s installée + Reconstruire + Reconstruire le cœur REST ? + Met à jour dash-node vers la dernière version. Le serveur est brièvement indisponible pendant la reconstruction ; ne quittez pas cet écran avant la fin. + La reconstruction nécessite une connexion internet. + Dashboard (REST API) + Cœur du système · toujours installé + Le dashboard est le cœur de l\'API REST du boîtier (dash-node) : un petit service Node sur 127.0.0.1:4000, servi par nginx et supervisé par pdsm. Il alimente chaque action de la bibliothèque dans l\'app : parcourir et télécharger des Livres, ajouter et supprimer du contenu Wikipedia et ZIM, les sessions Kolibri, les Cartes et l\'état du système. Contrairement aux modules de contenu, il ne s\'installe ni ne se supprime : il est fourni avec le boîtier et toujours présent. Lorsqu\'une version plus récente est disponible, vous pouvez le reconstruire sur place. + Points de terminaison REST des Livres, Wikipedia/ZIM, Kolibri et Cartes ; le vhost nginx ; et les points de terminaison de version et de reconstruction du système, tous servis sur 127.0.0.1:4000. + REST API + Cœur du système + GPL-2.0 + Reconstruction terminée — direction votre bibliothèque… + Compilation, tests et remplacement sur place… + Échec de la reconstruction + Reconstruit, mais les services n\'ont pas démarré à temps diff --git a/controller/app/src/main/res/values-gu/strings_k2go.xml b/controller/app/src/main/res/values-gu/strings_k2go.xml index 19f35e8a..b07c6d89 100644 --- a/controller/app/src/main/res/values-gu/strings_k2go.xml +++ b/controller/app/src/main/res/values-gu/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s કાઢી નાખ્યું કાઢી નાખ્યું — હમણાં માટે છુપાવ્યું, વર્તમાન ડાઉનલોડ પૂર્ણ થતાં સંપૂર્ણપણે દૂર થશે. કાઢી શકાયું નથી. કૃપા કરીને ફરી પ્રયાસ કરો. + + ડેશબોર્ડ ફરીથી બનાવાઈ રહ્યું છે… + Dashboard (REST API) + REST કોર + REST કોર · સ્થાપિત v%1$s + ફરીથી બનાવો + REST કોર ફરીથી બનાવવો? + dash-node ને નવીનતમ સંસ્કરણમાં અપડેટ કરે છે. ફરીથી બનાવતી વખતે સર્વર થોડા સમય માટે અનુપલબ્ધ રહે છે; પૂર્ણ ન થાય ત્યાં સુધી આ સ્ક્રીન છોડશો નહીં. + ફરીથી બનાવવા માટે ઇન્ટરનેટ કનેક્શન જરૂરી છે. + Dashboard (REST API) + સિસ્ટમ કોર · હંમેશા સ્થાપિત + ડેશબોર્ડ એ બોક્સનું REST API કોર છે (dash-node): 127.0.0.1:4000 પર એક નાની Node સેવા, જેને nginx પીરસે છે અને pdsm દેખરેખ રાખે છે. તે એપમાં દરેક લાઇબ્રેરી ક્રિયા ચલાવે છે: પુસ્તકો બ્રાઉઝ અને ડાઉનલોડ, Wikipedia અને ZIM સામગ્રી ઉમેરવી અને કાઢી નાખવી, Kolibri સત્રો, નકશા અને સિસ્ટમ સ્થિતિ. સામગ્રી મોડ્યુલોથી વિપરીત, તે સ્થાપિત કે દૂર કરાતું નથી — તે બોક્સ સાથે આવે છે અને હંમેશા હાજર રહે છે. જ્યારે નવું સંસ્કરણ ઉપલબ્ધ હોય, ત્યારે તમે તેને સ્થળ પર જ ફરીથી બનાવી શકો છો. + પુસ્તકો, Wikipedia/ZIM, Kolibri અને નકશા માટે REST એન્ડપોઇન્ટ; nginx vhost; અને સિસ્ટમ સંસ્કરણ અને ફરીથી બનાવવાના એન્ડપોઇન્ટ — બધા 127.0.0.1:4000 પર. + REST API + સિસ્ટમ કોર + GPL-2.0 + ફરીથી બનાવવું પૂર્ણ — તમને તમારી લાઇબ્રેરીમાં લઈ જવાઈ રહ્યા છે… + સ્થળ પર બનાવાઈ, ચકાસાઈ અને બદલાઈ રહ્યું છે… + ફરીથી બનાવવું નિષ્ફળ + ફરીથી બનાવ્યું, પણ સેવાઓ સમયસર શરૂ થઈ નહીં diff --git a/controller/app/src/main/res/values-hi/strings_k2go.xml b/controller/app/src/main/res/values-hi/strings_k2go.xml index 5403883c..6384773b 100644 --- a/controller/app/src/main/res/values-hi/strings_k2go.xml +++ b/controller/app/src/main/res/values-hi/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s हटाया गया हटाया गया — फ़िलहाल छिपाया गया, मौजूदा डाउनलोड पूरा होने पर पूरी तरह हटा दिया जाएगा। हटाया नहीं जा सका। कृपया पुनः प्रयास करें। + + डैशबोर्ड फिर से बनाया जा रहा है… + Dashboard (REST API) + REST कोर + REST कोर · स्थापित v%1$s + फिर से बनाएँ + REST कोर फिर से बनाएँ? + dash-node को नवीनतम संस्करण में अपडेट करता है. फिर से बनाने के दौरान सर्वर थोड़ी देर के लिए अनुपलब्ध रहता है; समाप्त होने तक यह स्क्रीन न छोड़ें. + फिर से बनाने के लिए इंटरनेट कनेक्शन आवश्यक है. + Dashboard (REST API) + सिस्टम कोर · हमेशा स्थापित + डैशबोर्ड बॉक्स का REST API कोर है (dash-node): 127.0.0.1:4000 पर एक छोटी Node सेवा, जिसे nginx परोसता है और pdsm देखरेख करता है. यह ऐप में हर लाइब्रेरी क्रिया को सक्षम करता है: किताबें ब्राउज़ और डाउनलोड करना, Wikipedia और ZIM सामग्री जोड़ना और हटाना, Kolibri सत्र, मानचित्र और सिस्टम स्थिति. सामग्री मॉड्यूल के विपरीत, इसे स्थापित या हटाया नहीं जाता — यह बॉक्स के साथ आता है और हमेशा मौजूद रहता है. जब कोई नया संस्करण उपलब्ध हो, आप इसे यथास्थान फिर से बना सकते हैं. + किताबें, Wikipedia/ZIM, Kolibri और मानचित्र के REST एंडपॉइंट; nginx vhost; और सिस्टम संस्करण व फिर से बनाने के एंडपॉइंट — सभी 127.0.0.1:4000 पर. + REST API + सिस्टम कोर + GPL-2.0 + फिर से बनाना पूरा हुआ — आपकी लाइब्रेरी पर ले जाया जा रहा है… + यथास्थान बनाया, परखा और बदला जा रहा है… + फिर से बनाना विफल + फिर से बनाया गया, लेकिन सेवाएँ समय पर शुरू नहीं हुईं diff --git a/controller/app/src/main/res/values-hu/strings_k2go.xml b/controller/app/src/main/res/values-hu/strings_k2go.xml index ab5de75a..dc4e8d71 100644 --- a/controller/app/src/main/res/values-hu/strings_k2go.xml +++ b/controller/app/src/main/res/values-hu/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s törölve Törölve — egyelőre elrejtve, a jelenlegi letöltés befejeztével teljesen eltávolítva. A törlés sikertelen. Próbáld újra. + + Irányítópult újraépítése… + Dashboard (REST API) + REST mag + REST mag · telepítve v%1$s + Újraépítés + Újraépíti a REST magot? + Frissíti a dash-node-ot a legújabb verzióra. A szerver az újraépítés alatt rövid ideig nem elérhető; ne hagyd el ezt a képernyőt, amíg be nem fejeződik. + Az újraépítéshez internetkapcsolat szükséges. + Dashboard (REST API) + Rendszermag · mindig telepítve + Az irányítópult a doboz REST API magja (dash-node): egy kis Node szolgáltatás a 127.0.0.1:4000 címen, amelyet az nginx szolgál ki és a pdsm felügyel. Ez működteti az alkalmazás minden könyvtári műveletét: Könyvek böngészése és letöltése, Wikipédia- és ZIM-tartalom hozzáadása és törlése, Kolibri munkamenetek, Térképek és rendszerállapot. A tartalommoduloktól eltérően nem telepíted vagy távolítod el — a dobozzal érkezik és mindig jelen van. Ha újabb verzió érhető el, helyben újraépítheted. + Könyvek, Wikipédia/ZIM, Kolibri és Térképek REST végpontjai; az nginx vhost; valamint a rendszerverzió és újraépítés végpontjai — mind a 127.0.0.1:4000 címen. + REST API + Rendszermag + GPL-2.0 + Újraépítés kész — átirányítunk a könyvtáradba… + Fordítás, tesztelés és helyben csere… + Az újraépítés sikertelen + Újraépítve, de a szolgáltatások nem indultak el időben diff --git a/controller/app/src/main/res/values-in/strings_k2go.xml b/controller/app/src/main/res/values-in/strings_k2go.xml index 596c1ed0..61cfdf4b 100644 --- a/controller/app/src/main/res/values-in/strings_k2go.xml +++ b/controller/app/src/main/res/values-in/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s dihapus Dihapus — disembunyikan untuk saat ini, dihapus sepenuhnya setelah unduhan saat ini selesai. Tidak dapat menghapus. Silakan coba lagi. + + Membangun ulang dasbor… + Dashboard (REST API) + Inti REST + Inti REST · terpasang v%1$s + Bangun ulang + Bangun ulang inti REST? + Memperbarui dash-node ke versi terbaru. Server sebentar tidak tersedia saat dibangun ulang; jangan tinggalkan layar ini sampai selesai. + Membangun ulang memerlukan koneksi internet. + Dashboard (REST API) + Inti sistem · selalu terpasang + Dasbor adalah inti REST API dari box (dash-node): layanan Node kecil di 127.0.0.1:4000, disajikan oleh nginx dan diawasi oleh pdsm. Ia menggerakkan setiap tindakan pustaka di dalam aplikasi: menjelajahi dan mengunduh Buku, menambah dan menghapus konten Wikipedia dan ZIM, sesi Kolibri, Peta, dan status sistem. Berbeda dengan modul konten, ia tidak dipasang atau dihapus — ia hadir bersama box dan selalu ada. Saat versi lebih baru tersedia, Anda dapat membangunnya ulang di tempat. + Endpoint REST untuk Buku, Wikipedia/ZIM, Kolibri, dan Peta; vhost nginx; serta endpoint versi sistem dan bangun ulang — semuanya di 127.0.0.1:4000. + REST API + Inti sistem + GPL-2.0 + Bangun ulang selesai — mengarahkan Anda ke pustaka… + Membangun, menguji, dan mengganti di tempat… + Bangun ulang gagal + Dibangun ulang, tetapi layanan tidak dimulai tepat waktu diff --git a/controller/app/src/main/res/values-it/strings_k2go.xml b/controller/app/src/main/res/values-it/strings_k2go.xml index 14088c3e..b2b2b3f5 100644 --- a/controller/app/src/main/res/values-it/strings_k2go.xml +++ b/controller/app/src/main/res/values-it/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s eliminato Eliminato — nascosto per ora, rimosso completamente al termine del download in corso. Impossibile eliminare. Riprova. + + Ricostruzione del dashboard… + Dashboard (REST API) + Nucleo REST + Nucleo REST · installato v%1$s + Ricostruisci + Ricostruire il nucleo REST? + Aggiorna dash-node all\'ultima versione. Il server è brevemente non disponibile durante la ricostruzione; non lasciare questa schermata finché non termina. + La ricostruzione richiede una connessione a internet. + Dashboard (REST API) + Nucleo di sistema · sempre installato + Il dashboard è il nucleo dell\'API REST della box (dash-node): un piccolo servizio Node su 127.0.0.1:4000, servito da nginx e supervisionato da pdsm. Alimenta ogni azione della libreria nell\'app: sfogliare e scaricare Libri, aggiungere ed eliminare contenuti Wikipedia e ZIM, sessioni Kolibri, Mappe e stato del sistema. A differenza dei moduli di contenuto, non si installa né si rimuove: è incluso nella box ed è sempre presente. Quando è disponibile una versione più recente, puoi ricostruirlo sul posto. + Endpoint REST di Libri, Wikipedia/ZIM, Kolibri e Mappe; il vhost nginx; e gli endpoint di versione e ricostruzione del sistema, tutti serviti su 127.0.0.1:4000. + REST API + Nucleo di sistema + GPL-2.0 + Ricostruzione completata — ti portiamo alla tua libreria… + Compilazione, test e sostituzione sul posto… + Ricostruzione non riuscita + Ricostruito, ma i servizi non si sono avviati in tempo diff --git a/controller/app/src/main/res/values-ja/strings_k2go.xml b/controller/app/src/main/res/values-ja/strings_k2go.xml index 837d8e1e..4eb4257e 100644 --- a/controller/app/src/main/res/values-ja/strings_k2go.xml +++ b/controller/app/src/main/res/values-ja/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s を削除しました 削除しました — 現在は非表示です。進行中のダウンロードが完了すると完全に削除されます。 削除できませんでした。もう一度お試しください。 + + ダッシュボードを再構築中… + Dashboard (REST API) + REST コア + REST コア · インストール済み v%1$s + 再構築 + REST コアを再構築しますか? + dash-node を最新バージョンに更新します。再構築中はサーバーが一時的に利用できなくなります。完了するまでこの画面を離れないでください。 + 再構築にはインターネット接続が必要です。 + Dashboard (REST API) + システムコア · 常にインストール済み + ダッシュボードはボックスの REST API コア(dash-node)です。127.0.0.1:4000 で動作する小さな Node サービスで、nginx が配信し pdsm が監視します。アプリ内のすべてのライブラリ操作を支えます。書籍の閲覧とダウンロード、Wikipedia と ZIM コンテンツの追加と削除、Kolibri セッション、地図、システム状態などです。コンテンツモジュールとは異なり、インストールや削除はできません。ボックスに付属し、常に存在します。新しいビルドが利用可能になると、その場で再構築できます。 + 書籍、Wikipedia/ZIM、Kolibri、地図の REST エンドポイント、nginx の vhost、システムバージョンと再構築のエンドポイント — すべて 127.0.0.1:4000 上。 + REST API + システムコア + GPL-2.0 + 再構築が完了しました — ライブラリに移動します… + その場でビルド・テスト・入れ替え中… + 再構築に失敗しました + 再構築しましたが、サービスが時間内に起動しませんでした diff --git a/controller/app/src/main/res/values-ko/strings_k2go.xml b/controller/app/src/main/res/values-ko/strings_k2go.xml index e18154a8..d476153f 100644 --- a/controller/app/src/main/res/values-ko/strings_k2go.xml +++ b/controller/app/src/main/res/values-ko/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s 삭제됨 삭제됨 — 지금은 숨김 상태이며, 현재 다운로드가 완료되면 완전히 제거됩니다. 삭제하지 못했습니다. 다시 시도해 주세요. + + 대시보드 재빌드 중… + Dashboard (REST API) + REST 코어 + REST 코어 · 설치됨 v%1$s + 재빌드 + REST 코어를 재빌드할까요? + dash-node를 최신 버전으로 업데이트합니다. 재빌드하는 동안 서버를 잠시 사용할 수 없습니다. 완료될 때까지 이 화면을 떠나지 마세요. + 재빌드하려면 인터넷 연결이 필요합니다. + Dashboard (REST API) + 시스템 코어 · 항상 설치됨 + 대시보드는 박스의 REST API 코어(dash-node)입니다. 127.0.0.1:4000에서 실행되는 작은 Node 서비스로, nginx가 제공하고 pdsm이 관리합니다. 앱의 모든 라이브러리 작업을 구동합니다: 도서 탐색 및 다운로드, Wikipedia 및 ZIM 콘텐츠 추가 및 삭제, Kolibri 세션, 지도, 시스템 상태. 콘텐츠 모듈과 달리 설치하거나 제거하지 않습니다 — 박스와 함께 제공되며 항상 존재합니다. 새 빌드가 있으면 제자리에서 재빌드할 수 있습니다. + 도서, Wikipedia/ZIM, Kolibri, 지도의 REST 엔드포인트, nginx vhost, 시스템 버전 및 재빌드 엔드포인트 — 모두 127.0.0.1:4000에서 제공됩니다. + REST API + 시스템 코어 + GPL-2.0 + 재빌드 완료 — 라이브러리로 이동합니다… + 제자리에서 빌드, 테스트, 교체 중… + 재빌드 실패 + 재빌드했지만 서비스가 제때 시작되지 않았습니다 diff --git a/controller/app/src/main/res/values-lt/strings_k2go.xml b/controller/app/src/main/res/values-lt/strings_k2go.xml index a8534869..7257fb3e 100644 --- a/controller/app/src/main/res/values-lt/strings_k2go.xml +++ b/controller/app/src/main/res/values-lt/strings_k2go.xml @@ -683,4 +683,24 @@ %1$s ištrinta Ištrinta — kol kas paslėpta, visiškai pašalinta pasibaigus dabartiniam atsisiuntimui. Nepavyko ištrinti. Bandykite dar kartą. + + Skydelis atkuriamas… + Dashboard (REST API) + REST branduolys + REST branduolys · įdiegta v%1$s + Atkurti + Atkurti REST branduolį? + Atnaujina dash-node į naujausią versiją. Atkūrimo metu serveris trumpam nepasiekiamas; nepalikite šio ekrano, kol nebus baigta. + Atkūrimui reikia interneto ryšio. + Dashboard (REST API) + Sistemos branduolys · visada įdiegtas + Skydelis yra įrenginio REST API branduolys (dash-node): maža Node paslauga adresu 127.0.0.1:4000, aptarnaujama nginx ir prižiūrima pdsm. Jis palaiko kiekvieną bibliotekos veiksmą programoje: Knygų naršymą ir atsisiuntimą, Wikipedia ir ZIM turinio pridėjimą bei šalinimą, Kolibri sesijas, Žemėlapius ir sistemos būseną. Skirtingai nei turinio moduliai, jis nediegiamas ir nešalinamas — pateikiamas su įrenginiu ir visada yra. Kai pasiekiama naujesnė versija, galite jį atkurti vietoje. + REST galiniai taškai Knygoms, Wikipedia/ZIM, Kolibri ir Žemėlapiams; nginx vhost; ir sistemos versijos bei atkūrimo galiniai taškai — visi adresu 127.0.0.1:4000. + REST API + Sistemos branduolys + GPL-2.0 + Atkūrimas baigtas — nukreipiame į jūsų biblioteką… + Kuriama, testuojama ir keičiama vietoje… + Atkurti nepavyko + Atkurta, bet paslaugos nepasileido laiku diff --git a/controller/app/src/main/res/values-nl/strings_k2go.xml b/controller/app/src/main/res/values-nl/strings_k2go.xml index ad67999e..8fa03e82 100644 --- a/controller/app/src/main/res/values-nl/strings_k2go.xml +++ b/controller/app/src/main/res/values-nl/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s verwijderd Verwijderd — voorlopig verborgen, volledig verwijderd zodra de huidige download klaar is. Verwijderen mislukt. Probeer het opnieuw. + + Dashboard opnieuw opbouwen… + Dashboard (REST API) + REST-kern + REST-kern · geïnstalleerd v%1$s + Opnieuw opbouwen + REST-kern opnieuw opbouwen? + Werkt dash-node bij naar de nieuwste versie. De server is tijdens het opnieuw opbouwen kort niet beschikbaar; verlaat dit scherm niet totdat het klaar is. + Opnieuw opbouwen vereist een internetverbinding. + Dashboard (REST API) + Systeemkern · altijd geïnstalleerd + Het dashboard is de REST-API-kern van de box (dash-node): een kleine Node-service op 127.0.0.1:4000, aangeboden via nginx en beheerd door pdsm. Het drijft elke bibliotheekactie in de app aan: Boeken bladeren en downloaden, Wikipedia- en ZIM-inhoud toevoegen en verwijderen, Kolibri-sessies, Kaarten en systeemstatus. Anders dan de inhoudsmodules installeer of verwijder je het niet: het hoort bij de box en is altijd aanwezig. Als er een nieuwere versie beschikbaar is, kun je het ter plekke opnieuw opbouwen. + REST-endpoints voor Boeken, Wikipedia/ZIM, Kolibri en Kaarten; de nginx-vhost; en endpoints voor systeemversie en opnieuw opbouwen, allemaal op 127.0.0.1:4000. + REST API + Systeemkern + GPL-2.0 + Opnieuw opbouwen voltooid — je gaat naar je bibliotheek… + Bouwen, testen en ter plekke vervangen… + Opnieuw opbouwen mislukt + Opnieuw opgebouwd, maar de services zijn niet op tijd gestart diff --git a/controller/app/src/main/res/values-no/strings_k2go.xml b/controller/app/src/main/res/values-no/strings_k2go.xml index e598cd60..76b603ad 100644 --- a/controller/app/src/main/res/values-no/strings_k2go.xml +++ b/controller/app/src/main/res/values-no/strings_k2go.xml @@ -683,4 +683,24 @@ %1$s slettet Slettet — skjult foreløpig, fjernes helt når den pågående nedlastingen er ferdig. Kunne ikke slette. Prøv igjen. + + Bygger kontrollpanelet på nytt… + Dashboard (REST API) + REST-kjerne + REST-kjerne · installert v%1$s + Bygg på nytt + Bygge REST-kjernen på nytt? + Oppdaterer dash-node til nyeste versjon. Serveren er kort utilgjengelig mens den bygges på nytt; ikke forlat denne skjermen før den er ferdig. + Ombygging krever internettforbindelse. + Dashboard (REST API) + Systemkjerne · alltid installert + Kontrollpanelet er boksens REST-API-kjerne (dash-node): en liten Node-tjeneste på 127.0.0.1:4000, levert av nginx og overvåket av pdsm. Den driver alle bibliotekhandlinger i appen: bla i og laste ned Bøker, legge til og slette Wikipedia- og ZIM-innhold, Kolibri-økter, Kart og systemstatus. I motsetning til innholdsmodulene installeres eller fjernes den ikke — den følger med boksen og er alltid til stede. Når en nyere versjon er tilgjengelig, kan du bygge den på nytt på stedet. + REST-endepunkter for Bøker, Wikipedia/ZIM, Kolibri og Kart; nginx-vhost; og endepunkter for systemversjon og ombygging — alle på 127.0.0.1:4000. + REST API + Systemkjerne + GPL-2.0 + Ombygging fullført — sender deg til biblioteket ditt… + Bygger, tester og bytter ut på stedet… + Ombygging mislyktes + Bygd på nytt, men tjenestene startet ikke i tide diff --git a/controller/app/src/main/res/values-pl/strings_k2go.xml b/controller/app/src/main/res/values-pl/strings_k2go.xml index c554c8aa..35dd8ad1 100644 --- a/controller/app/src/main/res/values-pl/strings_k2go.xml +++ b/controller/app/src/main/res/values-pl/strings_k2go.xml @@ -683,4 +683,24 @@ Usunięto %1$s Usunięto — na razie ukryte, zostanie całkowicie usunięte po zakończeniu bieżącego pobierania. Nie udało się usunąć. Spróbuj ponownie. + + Przebudowa pulpitu… + Dashboard (REST API) + Rdzeń REST + Rdzeń REST · zainstalowano v%1$s + Przebuduj + Przebudować rdzeń REST? + Aktualizuje dash-node do najnowszej wersji. Serwer jest przez chwilę niedostępny podczas przebudowy; nie opuszczaj tego ekranu, dopóki się nie zakończy. + Przebudowa wymaga połączenia z internetem. + Dashboard (REST API) + Rdzeń systemu · zawsze zainstalowany + Pulpit to rdzeń interfejsu REST API urządzenia (dash-node) — mała usługa Node pod adresem 127.0.0.1:4000, obsługiwana przez nginx i nadzorowana przez pdsm. Napędza każdą akcję biblioteki w aplikacji: przeglądanie i pobieranie Książek, dodawanie i usuwanie treści Wikipedii oraz ZIM, sesje Kolibri, Mapy i stan systemu. W przeciwieństwie do modułów treści nie instaluje się go ani nie usuwa — jest dostarczany z urządzeniem i zawsze obecny. Gdy dostępna jest nowsza wersja, możesz przebudować go w miejscu. + Punkty końcowe REST Książek, Wikipedii/ZIM, Kolibri i Map; vhost nginx; oraz punkty końcowe wersji systemu i przebudowy — wszystkie pod 127.0.0.1:4000. + REST API + Rdzeń systemu + GPL-2.0 + Przebudowa zakończona — przenosimy Cię do biblioteki… + Budowanie, testowanie i podmiana w miejscu… + Przebudowa nie powiodła się + Przebudowano, ale usługi nie uruchomiły się na czas diff --git a/controller/app/src/main/res/values-pt/strings_k2go.xml b/controller/app/src/main/res/values-pt/strings_k2go.xml index bedc2594..10031d6b 100644 --- a/controller/app/src/main/res/values-pt/strings_k2go.xml +++ b/controller/app/src/main/res/values-pt/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s excluído Excluído — oculto por ora, removido totalmente quando o download atual terminar. Não foi possível excluir. Tente novamente. + + Recompilando o dashboard… + Dashboard (REST API) + Núcleo REST + Núcleo REST · instalado v%1$s + Recompilar + Recompilar o núcleo REST? + Atualiza o dash-node para a versão mais recente. O servidor fica brevemente indisponível durante a recompilação; não saia desta tela até terminar. + A recompilação precisa de conexão com a internet. + Dashboard (REST API) + Núcleo do sistema · sempre instalado + O dashboard é o núcleo da API REST da caixa (dash-node): um pequeno serviço Node em 127.0.0.1:4000, servido pelo nginx e supervisionado pelo pdsm. Ele impulsiona todas as ações da biblioteca no app: navegar e baixar Livros, adicionar e excluir conteúdo da Wikipedia e ZIM, sessões do Kolibri, Mapas e status do sistema. Ao contrário dos módulos de conteúdo, ele não é instalado nem removido: vem com a caixa e está sempre presente. Quando há uma versão mais recente, você pode recompilá-lo no local. + Endpoints REST de Livros, Wikipedia/ZIM, Kolibri e Mapas; o vhost do nginx; e os endpoints de versão e recompilação do sistema, todos servidos em 127.0.0.1:4000. + REST API + Núcleo do sistema + GPL-2.0 + Recompilação concluída — indo para a sua biblioteca… + Compilando, testando e substituindo no local… + Falha na recompilação + Recompilado, mas os serviços não iniciaram a tempo diff --git a/controller/app/src/main/res/values-ro/strings_k2go.xml b/controller/app/src/main/res/values-ro/strings_k2go.xml index 5661397d..b1dfb5cd 100644 --- a/controller/app/src/main/res/values-ro/strings_k2go.xml +++ b/controller/app/src/main/res/values-ro/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s șters Șters — ascuns momentan, eliminat complet după ce se termină descărcarea curentă. Ștergerea a eșuat. Încearcă din nou. + + Se reconstruiește panoul… + Dashboard (REST API) + Nucleu REST + Nucleu REST · instalat v%1$s + Reconstruiește + Reconstruiești nucleul REST? + Actualizează dash-node la cea mai recentă versiune. Serverul este indisponibil pentru scurt timp în timpul reconstruirii; nu părăsi acest ecran până nu se termină. + Reconstruirea necesită o conexiune la internet. + Dashboard (REST API) + Nucleul sistemului · întotdeauna instalat + Panoul este nucleul API REST al cutiei (dash-node): un mic serviciu Node la 127.0.0.1:4000, servit de nginx și supravegheat de pdsm. Alimentează fiecare acțiune a bibliotecii din aplicație: răsfoirea și descărcarea Cărților, adăugarea și ștergerea conținutului Wikipedia și ZIM, sesiunile Kolibri, Hărțile și starea sistemului. Spre deosebire de modulele de conținut, nu se instalează și nu se elimină — vine cu cutia și este întotdeauna prezent. Când este disponibilă o versiune mai nouă, îl poți reconstrui pe loc. + Puncte finale REST pentru Cărți, Wikipedia/ZIM, Kolibri și Hărți; vhost-ul nginx; și punctele finale pentru versiunea sistemului și reconstruire — toate servite la 127.0.0.1:4000. + REST API + Nucleul sistemului + GPL-2.0 + Reconstruire finalizată — te ducem la biblioteca ta… + Se compilează, se testează și se înlocuiește pe loc… + Reconstruirea a eșuat + Reconstruit, dar serviciile nu au pornit la timp diff --git a/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml b/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml index 176bd726..a16a38a6 100644 --- a/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml +++ b/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s удалён Удалено — пока скрыто, полностью удалится после завершения текущей загрузки. Не удалось удалить. Повторите попытку. + + Пересборка панели… + Dashboard (REST API) + Ядро REST + Ядро REST · установлена v%1$s + Пересобрать + Пересобрать ядро REST? + Обновляет dash-node до последней версии. Во время пересборки сервер ненадолго недоступен; не покидайте этот экран, пока она не завершится. + Для пересборки нужно подключение к интернету. + Dashboard (REST API) + Ядро системы · всегда установлено + Панель — это ядро REST API устройства (dash-node): небольшой сервис Node на 127.0.0.1:4000, обслуживаемый nginx и управляемый pdsm. Оно обеспечивает все действия библиотеки в приложении: просмотр и загрузку Книг, добавление и удаление контента Wikipedia и ZIM, сессии Kolibri, Карты и состояние системы. В отличие от модулей контента, его нельзя установить или удалить — оно поставляется с устройством и всегда присутствует. Когда доступна более новая версия, вы можете пересобрать его на месте. + Конечные точки REST для Книг, Wikipedia/ZIM, Kolibri и Карт; vhost nginx; а также конечные точки версии системы и пересборки — всё на 127.0.0.1:4000. + REST API + Ядро системы + GPL-2.0 + Пересборка завершена — переходим в вашу библиотеку… + Сборка, тестирование и замена на месте… + Не удалось пересобрать + Пересобрано, но службы не запустились вовремя diff --git a/controller/app/src/main/res/values-sk/strings_k2go.xml b/controller/app/src/main/res/values-sk/strings_k2go.xml index 7979297a..57d87127 100644 --- a/controller/app/src/main/res/values-sk/strings_k2go.xml +++ b/controller/app/src/main/res/values-sk/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s odstránené Odstránené — zatiaľ skryté, úplne odstránené po dokončení aktuálneho sťahovania. Odstránenie zlyhalo. Skúste to znova. + + Prestavba nástenky… + Dashboard (REST API) + Jadro REST + Jadro REST · nainštalované v%1$s + Prestavať + Prestavať jadro REST? + Aktualizuje dash-node na najnovšiu verziu. Server je počas prestavby krátko nedostupný; neopúšťajte túto obrazovku, kým sa neskončí. + Prestavba vyžaduje pripojenie na internet. + Dashboard (REST API) + Jadro systému · vždy nainštalované + Nástenka je jadro REST API zariadenia (dash-node) — malá služba Node na 127.0.0.1:4000, obsluhovaná nginx a riadená pdsm. Poháňa každú akciu knižnice v aplikácii: prehliadanie a sťahovanie Kníh, pridávanie a mazanie obsahu Wikipédie a ZIM, relácie Kolibri, Mapy a stav systému. Na rozdiel od obsahových modulov sa neinštaluje ani neodstraňuje — je súčasťou zariadenia a je vždy prítomná. Keď je dostupná novšia verzia, môžete ju prestavať na mieste. + REST koncové body Kníh, Wikipédie/ZIM, Kolibri a Máp; nginx vhost; a koncové body verzie systému a prestavby — všetko na 127.0.0.1:4000. + REST API + Jadro systému + GPL-2.0 + Prestavba dokončená — presmerúvame vás do knižnice… + Zostavovanie, testovanie a výmena na mieste… + Prestavba zlyhala + Prestavané, ale služby sa nespustili včas diff --git a/controller/app/src/main/res/values-sr/strings_k2go.xml b/controller/app/src/main/res/values-sr/strings_k2go.xml index bcec2f6b..2caec8fd 100644 --- a/controller/app/src/main/res/values-sr/strings_k2go.xml +++ b/controller/app/src/main/res/values-sr/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s обрисано Обрисано — за сада сакривено, потпуно уклоњено када се заврши тренутно преузимање. Брисање није успело. Покушајте поново. + + Поновно грађење контролне табле… + Dashboard (REST API) + REST језгро + REST језгро · инсталирана v%1$s + Поново изгради + Поново изградити REST језгро? + Ажурира dash-node на најновију верзију. Сервер је накратко недоступан током поновне изградње; не напуштајте овај екран док се не заврши. + Поновна изградња захтева интернет везу. + Dashboard (REST API) + Системско језгро · увек инсталирано + Контролна табла је REST API језгро уређаја (dash-node): мала Node услуга на 127.0.0.1:4000, коју опслужује nginx а надгледа pdsm. Покреће сваку радњу библиотеке у апликацији: прегледање и преузимање Књига, додавање и брисање садржаја Wikipedia и ZIM, Kolibri сесије, Мапе и стање система. За разлику од модула садржаја, не инсталира се нити уклања — долази уз уређај и увек је присутна. Када је доступна новија верзија, можете је поново изградити на лицу места. + REST крајње тачке за Књиге, Wikipedia/ZIM, Kolibri и Мапе; nginx vhost; и крајње тачке за верзију система и поновну изградњу — све на 127.0.0.1:4000. + REST API + Системско језгро + GPL-2.0 + Поновна изградња завршена — водимо вас у вашу библиотеку… + Изградња, тестирање и замена на лицу места… + Поновна изградња није успела + Поново изграђено, али услуге нису покренуте на време diff --git a/controller/app/src/main/res/values-sw/strings_k2go.xml b/controller/app/src/main/res/values-sw/strings_k2go.xml index c28905b5..c0e74944 100644 --- a/controller/app/src/main/res/values-sw/strings_k2go.xml +++ b/controller/app/src/main/res/values-sw/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s imefutwa Imefutwa — imefichwa kwa sasa, itaondolewa kabisa upakuaji wa sasa utakapokamilika. Imeshindwa kufuta. Tafadhali jaribu tena. + + Inajenga upya dashibodi… + Dashboard (REST API) + Kiini cha REST + Kiini cha REST · imesakinishwa v%1$s + Jenga upya + Ujenge upya kiini cha REST? + Inasasisha dash-node hadi toleo jipya zaidi. Seva haipatikani kwa muda mfupi wakati wa kujengwa upya; usiondoke skrini hii hadi ikamilike. + Kujenga upya kunahitaji muunganisho wa intaneti. + Dashboard (REST API) + Kiini cha mfumo · kimesakinishwa daima + Dashibodi ni kiini cha REST API cha kisanduku (dash-node): huduma ndogo ya Node kwenye 127.0.0.1:4000, inayotolewa na nginx na kusimamiwa na pdsm. Inaendesha kila kitendo cha maktaba ndani ya programu: kuvinjari na kupakua Vitabu, kuongeza na kufuta maudhui ya Wikipedia na ZIM, vipindi vya Kolibri, Ramani na hali ya mfumo. Tofauti na moduli za maudhui, haisakinishwi wala kuondolewa — inakuja na kisanduku na ipo daima. Toleo jipya linapopatikana, unaweza kukijenga upya papo hapo. + Ncha za REST za Vitabu, Wikipedia/ZIM, Kolibri na Ramani; vhost ya nginx; na ncha za toleo la mfumo na kujenga upya — zote kwenye 127.0.0.1:4000. + REST API + Kiini cha mfumo + GPL-2.0 + Kujenga upya kumekamilika — tunakupeleka kwenye maktaba yako… + Inajenga, inajaribu na kubadilisha papo hapo… + Kujenga upya kumeshindwa + Imejengwa upya, lakini huduma hazikuanza kwa wakati diff --git a/controller/app/src/main/res/values-ta/strings_k2go.xml b/controller/app/src/main/res/values-ta/strings_k2go.xml index ce5f5c11..ff07985a 100644 --- a/controller/app/src/main/res/values-ta/strings_k2go.xml +++ b/controller/app/src/main/res/values-ta/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s நீக்கப்பட்டது நீக்கப்பட்டது — தற்போது மறைக்கப்பட்டது, தற்போதைய பதிவிறக்கம் முடிந்ததும் முழுமையாக அகற்றப்படும். நீக்க முடியவில்லை. மீண்டும் முயற்சிக்கவும். + + டாஷ்போர்டு மீண்டும் உருவாக்கப்படுகிறது… + Dashboard (REST API) + REST கரு + REST கரு · நிறுவப்பட்டது v%1$s + மீண்டும் உருவாக்கு + REST கருவை மீண்டும் உருவாக்கவா? + dash-node ஐ சமீபத்திய பதிப்பிற்குப் புதுப்பிக்கிறது. மீண்டும் உருவாக்கும்போது சேவையகம் சிறிது நேரம் கிடைக்காது; முடியும் வரை இந்தத் திரையை விட்டு வெளியேறாதீர்கள். + மீண்டும் உருவாக்க இணைய இணைப்பு தேவை. + Dashboard (REST API) + கணினி கரு · எப்போதும் நிறுவப்பட்டது + டாஷ்போர்டு என்பது பெட்டியின் REST API கரு (dash-node): 127.0.0.1:4000 இல் இயங்கும் ஒரு சிறிய Node சேவை, nginx வழங்கி pdsm மேற்பார்வையிடுகிறது. இது பயன்பாட்டில் ஒவ்வொரு நூலக செயலையும் இயக்குகிறது: புத்தகங்களை உலாவுதல் மற்றும் பதிவிறக்குதல், Wikipedia மற்றும் ZIM உள்ளடக்கத்தைச் சேர்த்தல் மற்றும் நீக்குதல், Kolibri அமர்வுகள், வரைபடங்கள் மற்றும் கணினி நிலை. உள்ளடக்க தொகுதிகளைப் போலல்லாமல், இதை நிறுவவோ அகற்றவோ முடியாது — இது பெட்டியுடன் வருகிறது, எப்போதும் இருக்கும். புதிய பதிப்பு கிடைக்கும்போது அதை இடத்திலேயே மீண்டும் உருவாக்கலாம். + புத்தகங்கள், Wikipedia/ZIM, Kolibri மற்றும் வரைபடங்களுக்கான REST இறுதிப்புள்ளிகள்; nginx vhost; மற்றும் கணினி பதிப்பு மற்றும் மீண்டும் உருவாக்கும் இறுதிப்புள்ளிகள் — அனைத்தும் 127.0.0.1:4000 இல். + REST API + கணினி கரு + GPL-2.0 + மீண்டும் உருவாக்கம் முடிந்தது — உங்கள் நூலகத்திற்கு அனுப்பப்படுகிறீர்கள்… + இடத்திலேயே உருவாக்கப்பட்டு, சோதிக்கப்பட்டு, மாற்றப்படுகிறது… + மீண்டும் உருவாக்கம் தோல்வியடைந்தது + மீண்டும் உருவாக்கப்பட்டது, ஆனால் சேவைகள் சரியான நேரத்தில் தொடங்கவில்லை diff --git a/controller/app/src/main/res/values-tr/strings_k2go.xml b/controller/app/src/main/res/values-tr/strings_k2go.xml index 6ac8a675..69763569 100644 --- a/controller/app/src/main/res/values-tr/strings_k2go.xml +++ b/controller/app/src/main/res/values-tr/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s silindi Silindi — şimdilik gizlendi, geçerli indirme tamamlanınca tamamen kaldırılacak. Silinemedi. Lütfen tekrar deneyin. + + Pano yeniden oluşturuluyor… + Dashboard (REST API) + REST çekirdeği + REST çekirdeği · v%1$s yüklü + Yeniden oluştur + REST çekirdeği yeniden oluşturulsun mu? + dash-node\'u en son sürüme günceller. Yeniden oluşturma sırasında sunucu kısa süre kullanılamaz; bitene kadar bu ekrandan ayrılmayın. + Yeniden oluşturma için internet bağlantısı gerekir. + Dashboard (REST API) + Sistem çekirdeği · her zaman yüklü + Pano, kutunun REST API çekirdeğidir (dash-node): 127.0.0.1:4000 üzerinde çalışan, nginx tarafından sunulan ve pdsm tarafından denetlenen küçük bir Node hizmeti. Uygulamadaki her kitaplık eylemini çalıştırır: Kitaplara göz atma ve indirme, Wikipedia ve ZIM içeriği ekleme ve silme, Kolibri oturumları, Haritalar ve sistem durumu. İçerik modüllerinin aksine yüklenmez veya kaldırılmaz — kutuyla birlikte gelir ve her zaman mevcuttur. Daha yeni bir sürüm olduğunda onu yerinde yeniden oluşturabilirsiniz. + Kitaplar, Wikipedia/ZIM, Kolibri ve Haritalar için REST uç noktaları; nginx vhost; ve sistem sürümü ile yeniden oluşturma uç noktaları — tümü 127.0.0.1:4000 üzerinde. + REST API + Sistem çekirdeği + GPL-2.0 + Yeniden oluşturma tamamlandı — kitaplığınıza yönlendiriliyorsunuz… + Yerinde oluşturuluyor, test ediliyor ve değiştiriliyor… + Yeniden oluşturma başarısız + Yeniden oluşturuldu ama hizmetler zamanında başlamadı diff --git a/controller/app/src/main/res/values-uk/strings_k2go.xml b/controller/app/src/main/res/values-uk/strings_k2go.xml index c93f89f0..07b2ce87 100644 --- a/controller/app/src/main/res/values-uk/strings_k2go.xml +++ b/controller/app/src/main/res/values-uk/strings_k2go.xml @@ -673,4 +673,24 @@ %1$s видалено Видалено — поки що приховано, повністю видалиться після завершення поточного завантаження. Не вдалося видалити. Спробуйте ще раз. + + Перезбирання панелі… + Dashboard (REST API) + Ядро REST + Ядро REST · встановлено v%1$s + Перезібрати + Перезібрати ядро REST? + Оновлює dash-node до найновішої версії. Під час перезбирання сервер ненадовго недоступний; не залишайте цей екран, доки воно не завершиться. + Для перезбирання потрібне з\'єднання з інтернетом. + Dashboard (REST API) + Ядро системи · завжди встановлено + Панель — це ядро REST API пристрою (dash-node): невеликий сервіс Node на 127.0.0.1:4000, який обслуговує nginx і контролює pdsm. Воно забезпечує всі дії бібліотеки в застосунку: перегляд і завантаження Книг, додавання й видалення вмісту Wikipedia та ZIM, сесії Kolibri, Карти та стан системи. На відміну від модулів вмісту, його не встановлюють і не видаляють — воно постачається з пристроєм і завжди присутнє. Коли доступна новіша версія, ви можете перезібрати його на місці. + Кінцеві точки REST для Книг, Wikipedia/ZIM, Kolibri та Карт; vhost nginx; а також кінцеві точки версії системи та перезбирання — усе на 127.0.0.1:4000. + REST API + Ядро системи + GPL-2.0 + Перезбирання завершено — переходимо до вашої бібліотеки… + Збирання, тестування та заміна на місці… + Не вдалося перезібрати + Перезібрано, але служби не запустилися вчасно diff --git a/controller/app/src/main/res/values-vi/strings_k2go.xml b/controller/app/src/main/res/values-vi/strings_k2go.xml index dc8f8ee2..1bc83637 100644 --- a/controller/app/src/main/res/values-vi/strings_k2go.xml +++ b/controller/app/src/main/res/values-vi/strings_k2go.xml @@ -673,4 +673,24 @@ Đã xóa %1$s Đã xóa — tạm ẩn, sẽ bị xóa hoàn toàn khi tải xuống hiện tại hoàn tất. Không thể xóa. Vui lòng thử lại. + + Đang xây dựng lại bảng điều khiển… + Dashboard (REST API) + Lõi REST + Lõi REST · đã cài v%1$s + Xây dựng lại + Xây dựng lại lõi REST? + Cập nhật dash-node lên phiên bản mới nhất. Máy chủ tạm thời không khả dụng trong khi xây dựng lại; đừng rời màn hình này cho đến khi hoàn tất. + Việc xây dựng lại cần kết nối internet. + Dashboard (REST API) + Lõi hệ thống · luôn được cài đặt + Bảng điều khiển là lõi REST API của hộp (dash-node): một dịch vụ Node nhỏ tại 127.0.0.1:4000, do nginx phục vụ và pdsm giám sát. Nó cung cấp mọi thao tác thư viện trong ứng dụng: duyệt và tải Sách, thêm và xóa nội dung Wikipedia và ZIM, phiên Kolibri, Bản đồ và trạng thái hệ thống. Khác với các mô-đun nội dung, nó không được cài đặt hay gỡ bỏ — nó đi kèm với hộp và luôn hiện diện. Khi có bản mới hơn, bạn có thể xây dựng lại nó tại chỗ. + Các điểm cuối REST cho Sách, Wikipedia/ZIM, Kolibri và Bản đồ; vhost nginx; và các điểm cuối phiên bản hệ thống và xây dựng lại — tất cả tại 127.0.0.1:4000. + REST API + Lõi hệ thống + GPL-2.0 + Xây dựng lại hoàn tất — đang đưa bạn đến thư viện… + Đang xây dựng, kiểm thử và thay thế tại chỗ… + Xây dựng lại thất bại + Đã xây dựng lại, nhưng các dịch vụ không khởi động kịp thời diff --git a/controller/app/src/main/res/values-yo/strings_k2go.xml b/controller/app/src/main/res/values-yo/strings_k2go.xml index bcae5bcc..0376c080 100644 --- a/controller/app/src/main/res/values-yo/strings_k2go.xml +++ b/controller/app/src/main/res/values-yo/strings_k2go.xml @@ -673,4 +673,24 @@ A ti paarẹ́ %1$s A ti paarẹ́ — a fi pamọ́ fún ìsinsìnyí, yóò yọ kúrò pátápátá nígbà tí ìgbàsílẹ̀ lọ́wọ́lọ́wọ́ bá parí. Kò lè paarẹ́. Jọ̀wọ́ gbìyànjú lẹ́ẹ̀kansi. + + Atúnkọ́ dasibọ́ọ̀dù… + Dashboard (REST API) + Kókó REST + Kókó REST · fi sori ẹrọ v%1$s + Tún kọ́ + Tún kókó REST kọ́? + Ó ń ṣe àfikún dash-node sí ẹ̀yà tuntun. Sáfà kì yóò sí fún ìgbà díẹ̀ nígbà tí ó ń tún kọ́; má ṣe kúrò ní ojú-iwé yìí títí yóò fi parí. + Àtúnkọ́ nílò ìsopọ̀ íntánẹ́ẹ̀tì. + Dashboard (REST API) + Kókó ètò · fi sori ẹrọ nígbà gbogbo + Dasibọ́ọ̀dù ni kókó REST API ti àpótí náà (dash-node): iṣẹ́ Node kékeré kan ní 127.0.0.1:4000, tí nginx ń ṣe ìránṣẹ́ tí pdsm sì ń bójútó. Ó ń ṣe àtìlẹ́yìn fún gbogbo iṣẹ́ ilé-ìkàwé nínú áàpù: ríràwo àti gbígba Àwọn Ìwé, fífi kún àti pípa àkóónú Wikipedia àti ZIM rẹ́, àwọn ìpàdé Kolibri, Àwọn Máàpù àti ipò ètò. Yàtọ̀ sí àwọn módù àkóónú, kì í ṣe ohun tí o fi sori ẹrọ tàbí yọ kúrò — ó wá pẹ̀lú àpótí náà ó sì wà nígbà gbogbo. Nígbà tí ẹ̀yà tuntun bá wà, o lè tún un kọ́ níbẹ̀. + Àwọn ojú-ọ̀nà REST fún Àwọn Ìwé, Wikipedia/ZIM, Kolibri àti Àwọn Máàpù; vhost nginx; àti àwọn ojú-ọ̀nà ẹ̀yà ètò àti àtúnkọ́ — gbogbo rẹ̀ ní 127.0.0.1:4000. + REST API + Kókó ètò + GPL-2.0 + Àtúnkọ́ parí — a ń mú ọ lọ sí ilé-ìkàwé rẹ… + Ń kọ́, ń dánwò, ń pààrọ̀ níbẹ̀… + Àtúnkọ́ kùnà + A tún un kọ́, ṣùgbọ́n àwọn iṣẹ́ kò bẹ̀rẹ̀ ní àkókò diff --git a/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml b/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml index 32607582..253a59e6 100644 --- a/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml +++ b/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml @@ -673,4 +673,24 @@ 已删除 %1$s 已删除 —— 暂时隐藏,当前下载完成后将彻底移除。 无法删除。请重试。 + + 正在重建仪表板… + Dashboard (REST API) + REST 核心 + REST 核心 · 已安装 v%1$s + 重建 + 重建 REST 核心? + 将 dash-node 更新到最新版本。重建期间服务器会短暂不可用;完成前请勿离开此界面。 + 重建需要互联网连接。 + Dashboard (REST API) + 系统核心 · 始终已安装 + 仪表板是设备的 REST API 核心(dash-node):运行在 127.0.0.1:4000 上的小型 Node 服务,由 nginx 提供并由 pdsm 监管。它驱动应用内的每一项库操作:浏览和下载图书、添加和删除 Wikipedia 和 ZIM 内容、Kolibri 会话、地图以及系统状态。与内容模块不同,它无法安装或删除——它随设备提供且始终存在。有更新版本时,你可以就地重建它。 + 图书、Wikipedia/ZIM、Kolibri 和地图的 REST 端点;nginx 虚拟主机;以及系统版本和重建端点——全部位于 127.0.0.1:4000。 + REST API + 系统核心 + GPL-2.0 + 重建完成 — 正在带你前往你的库… + 正在就地构建、测试并替换… + 重建失败 + 已重建,但服务未能及时启动