@naviyra/cloud-panel 0.0.2 → 0.0.3-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { cookies } from "next/headers";
4
4
  import { db } from "../../../lib/db.mjs";
5
5
  import { session, signIn } from "../../../lib/auth.mjs";
6
+ import { originAllowed } from "../../../lib/request-origin.mjs";
6
7
  import { agent, reconcile } from "../../../lib/agent.mjs";
7
8
  import { projectInput, idSchema, MAX_ZIP_BYTES } from "@naviyra/shared";
8
9
  import {
@@ -31,7 +32,7 @@ async function handle(req, { params }) {
31
32
  const method = req.method;
32
33
  if (method !== "GET") {
33
34
  const expected = process.env.PUBLIC_ORIGIN || new URL(req.url).origin;
34
- if (req.headers.get("origin") !== expected)
35
+ if (!originAllowed(req.headers.get("origin"), expected))
35
36
  return json({ error: "Invalid request origin" }, 403);
36
37
  }
37
38
  if (route === "auth/login" && method === "POST") {
@@ -1710,26 +1710,73 @@ html[data-theme="dark"] .danger-text {
1710
1710
  margin-bottom: 10px;
1711
1711
  }
1712
1712
  .database-create {
1713
- display: flex;
1714
- flex-wrap: wrap;
1715
- align-items: flex-end;
1713
+ display: grid;
1714
+ grid-template-columns: minmax(240px, 340px) minmax(220px, 1fr) auto;
1715
+ align-items: end;
1716
1716
  gap: 12px 16px;
1717
1717
  padding: 18px 23px 20px;
1718
1718
  }
1719
1719
  .database-create label {
1720
- flex: 1 1 220px;
1721
- max-width: 460px;
1722
1720
  margin-bottom: 0;
1721
+ min-width: 0;
1723
1722
  }
1724
1723
  .database-create small {
1725
1724
  margin-top: 4px;
1725
+ line-height: 1.45;
1726
1726
  }
1727
- .database-create input {
1727
+ .database-create input,
1728
+ .database-create select {
1728
1729
  height: 40px;
1729
1730
  margin-top: 8px;
1731
+ box-sizing: border-box;
1732
+ }
1733
+ .database-create select {
1734
+ display: block;
1735
+ width: 100%;
1736
+ padding: 0 36px 0 12px;
1737
+ border: 1px solid #dde4e7;
1738
+ border-radius: 6px;
1739
+ background-color: var(--surface);
1740
+ color: #3e5360;
1741
+ font-size: 12px;
1742
+ appearance: none;
1743
+ background-image:
1744
+ linear-gradient(45deg, transparent 50%, var(--muted) 50%),
1745
+ linear-gradient(135deg, var(--muted) 50%, transparent 50%);
1746
+ background-position:
1747
+ calc(100% - 18px) center,
1748
+ calc(100% - 13px) center;
1749
+ background-size:
1750
+ 5px 5px,
1751
+ 5px 5px;
1752
+ background-repeat: no-repeat;
1730
1753
  }
1731
1754
  .database-create .primary {
1732
1755
  height: 40px;
1756
+ min-height: 40px;
1757
+ }
1758
+ html[data-theme="dark"] .database-create select {
1759
+ background-color: var(--surface);
1760
+ color: var(--ink);
1761
+ border-color: var(--line);
1762
+ background-image:
1763
+ linear-gradient(45deg, transparent 50%, var(--muted) 50%),
1764
+ linear-gradient(135deg, var(--muted) 50%, transparent 50%);
1765
+ background-position:
1766
+ calc(100% - 18px) center,
1767
+ calc(100% - 13px) center;
1768
+ background-size:
1769
+ 5px 5px,
1770
+ 5px 5px;
1771
+ background-repeat: no-repeat;
1772
+ }
1773
+ @media (max-width: 720px) {
1774
+ .database-create {
1775
+ grid-template-columns: 1fr;
1776
+ }
1777
+ .database-create .primary {
1778
+ justify-self: start;
1779
+ }
1733
1780
  }
1734
1781
  .mail-address {
1735
1782
  display: flex;
@@ -0,0 +1,29 @@
1
+ const LOOPBACK = new Set(["localhost", "127.0.0.1", "::1"]);
2
+
3
+ function parseOrigin(value) {
4
+ if (!value || typeof value !== "string") return null;
5
+ try {
6
+ const url = new URL(value);
7
+ if (url.origin === "null") return null;
8
+ return url;
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+
14
+ function hostname(url) {
15
+ return url.hostname.replace(/^\[|\]$/g, "");
16
+ }
17
+
18
+ function sameSite(left, right) {
19
+ if (left.origin === right.origin) return true;
20
+ if (left.protocol !== right.protocol || left.port !== right.port) return false;
21
+ return LOOPBACK.has(hostname(left)) && LOOPBACK.has(hostname(right));
22
+ }
23
+
24
+ export function originAllowed(originHeader, expected) {
25
+ const origin = parseOrigin(originHeader);
26
+ const target = parseOrigin(expected);
27
+ if (!origin || !target) return false;
28
+ return sameSite(origin, target);
29
+ }
package/bin/naviyra.mjs CHANGED
@@ -189,6 +189,11 @@ See docs/managed-runtime.md in the installed folder.`);
189
189
  // created pg already while leaving Next.js or the agent dependencies missing.
190
190
  console.log("Checking and installing dependencies…");
191
191
  await run("npm", ["install", "--omit=dev"], target);
192
+ const controlPath = path.join(source, "scripts", "panel-control.mjs");
193
+ if (await exists(controlPath)) {
194
+ const { linkBin } = await import(pathToFileURL(controlPath).href);
195
+ await linkBin(target);
196
+ }
192
197
  await run(process.execPath, ["scripts/setup-local.mjs"], target);
193
198
  const background = await enableBackground(target);
194
199
  if (background) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naviyra/cloud-panel",
3
- "version": "0.0.2",
3
+ "version": "0.0.3-beta2",
4
4
  "description": "Self-hosted Naviyra panel for projects, databases, DNS, and email routing.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,8 @@
21
21
  "README.md"
22
22
  ],
23
23
  "publishConfig": {
24
- "access": "public"
24
+ "access": "public",
25
+ "tag": "beta"
25
26
  },
26
27
  "workspaces": [
27
28
  "apps/*",
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { access, readFile, rm } from "node:fs/promises";
2
+ import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import {
5
5
  TASK_NAME,
@@ -137,6 +137,24 @@ async function readStatus(dir, taskState) {
137
137
  };
138
138
  }
139
139
 
140
+ export async function linkBin(root) {
141
+ const binDir = path.join(root, "node_modules", ".bin");
142
+ await mkdir(binDir, { recursive: true });
143
+ await writeFile(
144
+ path.join(binDir, "cloud-panel.cmd"),
145
+ '@ECHO off\r\nnode "%~dp0\\..\\..\\bin\\naviyra.mjs" %*\r\n',
146
+ );
147
+ await writeFile(
148
+ path.join(binDir, "cloud-panel.ps1"),
149
+ [
150
+ "$node = (Get-Command node -ErrorAction Stop).Source",
151
+ '& $node (Join-Path $PSScriptRoot "..\\..\\bin\\naviyra.mjs") @args',
152
+ "exit $LASTEXITCODE",
153
+ "",
154
+ ].join("\n"),
155
+ );
156
+ }
157
+
140
158
  export async function execute(command, options = {}) {
141
159
  const located = await locate(options);
142
160
  if (command === "status") {
@@ -186,6 +204,7 @@ export async function execute(command, options = {}) {
186
204
  throw Error("Run npx @naviyra/cloud-panel update so the newest package can replace this install.");
187
205
  await options.copy(options.source, root);
188
206
  await options.install(root);
207
+ await linkBin(root);
189
208
  const started = await startBootTask(run);
190
209
  if (!started.ok) throw Error(started.message || "Update copied, but Naviyra did not start.");
191
210
  console.log(`Updated Naviyra in ${root}. Check it with: npx @naviyra/cloud-panel status`);