@naviyra/cloud-panel 0.0.3-beta → 0.0.3-beta3
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.
- package/apps/agent/src/cloudflare.mjs +858 -733
- package/apps/agent/src/files.mjs +162 -0
- package/apps/agent/src/host-tools.mjs +518 -0
- package/apps/agent/src/native.mjs +157 -32
- package/apps/agent/src/runtime.mjs +70 -37
- package/apps/agent/src/server.mjs +61 -6
- package/apps/panel/app/api/[...path]/route.js +96 -14
- package/apps/panel/app/globals.css +313 -7
- package/apps/panel/app/layout.jsx +2 -1
- package/apps/panel/app/login/page.jsx +3 -8
- package/apps/panel/app/page.jsx +17 -7
- package/apps/panel/app/ui/cloudflare.jsx +72 -47
- package/apps/panel/app/ui/confirm.jsx +109 -0
- package/apps/panel/app/ui/dashboard.jsx +87 -72
- package/apps/panel/app/ui/databases.jsx +168 -59
- package/apps/panel/app/ui/project-tools.jsx +604 -0
- package/apps/panel/app/ui/toast.jsx +90 -0
- package/apps/panel/lib/agent.mjs +13 -2
- package/apps/panel/lib/databases.mjs +120 -0
- package/apps/panel/lib/request-origin.mjs +29 -0
- package/package.json +1 -1
- package/packages/shared/index.mjs +49 -0
- package/scripts/panel-control.mjs +214 -214
- package/scripts/service-supervisor.mjs +164 -164
- package/scripts/windows-boot.mjs +259 -259
|
@@ -3,24 +3,27 @@ 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
|
-
import { projectInput,
|
|
8
|
+
import { projectInput, runtimeConfig, idSchema } from "@naviyra/shared";
|
|
8
9
|
import {
|
|
9
10
|
listDatabases,
|
|
10
11
|
createDatabase,
|
|
11
12
|
deleteDatabase,
|
|
12
13
|
resetDatabasePassword,
|
|
14
|
+
listNativeDatabases,
|
|
15
|
+
browseNativeDatabase,
|
|
13
16
|
} from "../../../lib/databases.mjs";
|
|
14
17
|
export const runtime = "nodejs";
|
|
15
18
|
export const dynamic = "force-dynamic";
|
|
16
19
|
const attempts = (globalThis.naviyraAttempts ||= new Map());
|
|
17
20
|
const json = (data, status = 200) =>
|
|
18
21
|
Response.json(data, { status, headers: { "Cache-Control": "no-store" } });
|
|
19
|
-
async function readJson(req) {
|
|
20
|
-
if (Number(req.headers.get("content-length")) >
|
|
22
|
+
async function readJson(req, limit = 8192) {
|
|
23
|
+
if (Number(req.headers.get("content-length")) > limit)
|
|
21
24
|
throw Object.assign(Error("Request too large"), { status: 413 });
|
|
22
25
|
const text = await req.text();
|
|
23
|
-
if (text.length >
|
|
26
|
+
if (text.length > limit)
|
|
24
27
|
throw Object.assign(Error("Request too large"), { status: 413 });
|
|
25
28
|
return JSON.parse(text);
|
|
26
29
|
}
|
|
@@ -31,7 +34,7 @@ async function handle(req, { params }) {
|
|
|
31
34
|
const method = req.method;
|
|
32
35
|
if (method !== "GET") {
|
|
33
36
|
const expected = process.env.PUBLIC_ORIGIN || new URL(req.url).origin;
|
|
34
|
-
if (req.headers.get("origin")
|
|
37
|
+
if (!originAllowed(req.headers.get("origin"), expected))
|
|
35
38
|
return json({ error: "Invalid request origin" }, 403);
|
|
36
39
|
}
|
|
37
40
|
if (route === "auth/login" && method === "POST") {
|
|
@@ -90,6 +93,15 @@ async function handle(req, { params }) {
|
|
|
90
93
|
}
|
|
91
94
|
if (route === "databases" && method === "GET")
|
|
92
95
|
return json(await listDatabases(user.id));
|
|
96
|
+
if (parts[0] === "databases" && parts[1] === "native") {
|
|
97
|
+
if (user.role !== "admin")
|
|
98
|
+
return json({ error: "Administrator access required" }, 403);
|
|
99
|
+
if (method !== "GET") return json({ error: "Method not allowed" }, 405);
|
|
100
|
+
if (parts.length === 2) return json(await listNativeDatabases());
|
|
101
|
+
if (parts.length === 3 || parts.length === 4)
|
|
102
|
+
return json(await browseNativeDatabase(parts[2], parts[3]));
|
|
103
|
+
return json({ error: "Not found" }, 404);
|
|
104
|
+
}
|
|
93
105
|
if (route === "databases" && method === "POST") {
|
|
94
106
|
const body = await readJson(req);
|
|
95
107
|
const created = await createDatabase(
|
|
@@ -312,6 +324,82 @@ async function handle(req, { params }) {
|
|
|
312
324
|
])
|
|
313
325
|
).rows[0];
|
|
314
326
|
if (!project) return json({ error: "Project not found" }, 404);
|
|
327
|
+
if (action === "files") {
|
|
328
|
+
const search = new URL(req.url).search || "";
|
|
329
|
+
if (method === "GET" && new URL(req.url).searchParams.get("download") === "1") {
|
|
330
|
+
const response = await agent(`/projects/${id}/files${search}`, {
|
|
331
|
+
raw: true,
|
|
332
|
+
});
|
|
333
|
+
return new Response(response.body, {
|
|
334
|
+
status: 200,
|
|
335
|
+
headers: {
|
|
336
|
+
"Content-Type": "application/octet-stream",
|
|
337
|
+
"Content-Disposition":
|
|
338
|
+
response.headers.get("content-disposition") || "attachment",
|
|
339
|
+
"Cache-Control": "no-store",
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (method === "GET")
|
|
344
|
+
return json(await agent(`/projects/${id}/files${search}`));
|
|
345
|
+
if (method === "POST") {
|
|
346
|
+
const length = Number(req.headers.get("content-length") || 0);
|
|
347
|
+
if (length > 32 * 1024 * 1024)
|
|
348
|
+
return json({ error: "File is larger than 32 MB" }, 413);
|
|
349
|
+
return json(
|
|
350
|
+
await agent(`/projects/${id}/files${search}`, {
|
|
351
|
+
method: "POST",
|
|
352
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
353
|
+
body: req.body,
|
|
354
|
+
duplex: "half",
|
|
355
|
+
signal: AbortSignal.timeout(10 * 60 * 1000),
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (method === "PUT") {
|
|
360
|
+
const body = await readJson(req, 2 * 1024 * 1024);
|
|
361
|
+
return json(
|
|
362
|
+
await agent(`/projects/${id}/files`, {
|
|
363
|
+
method: "PUT",
|
|
364
|
+
body: JSON.stringify(body),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (method === "DELETE")
|
|
369
|
+
return json(
|
|
370
|
+
await agent(`/projects/${id}/files${search}`, { method: "DELETE" }),
|
|
371
|
+
);
|
|
372
|
+
return json({ error: "Method not allowed" }, 405);
|
|
373
|
+
}
|
|
374
|
+
if (method === "PUT" && action === "config") {
|
|
375
|
+
const body = await readJson(req, 32768);
|
|
376
|
+
const types = new Set([
|
|
377
|
+
"nodejs",
|
|
378
|
+
"python",
|
|
379
|
+
"php",
|
|
380
|
+
"go",
|
|
381
|
+
"static",
|
|
382
|
+
"custom",
|
|
383
|
+
]);
|
|
384
|
+
const type = types.has(body.type) ? body.type : project.type;
|
|
385
|
+
const config = runtimeConfig.parse({
|
|
386
|
+
...(project.runtime_config || {}),
|
|
387
|
+
...(body.runtime_config || {}),
|
|
388
|
+
});
|
|
389
|
+
await db.query(
|
|
390
|
+
"UPDATE projects SET type=$2, runtime_config=$3, updated_at=now() WHERE id=$1",
|
|
391
|
+
[id, type, JSON.stringify(config)],
|
|
392
|
+
);
|
|
393
|
+
await agent("/projects", {
|
|
394
|
+
method: "POST",
|
|
395
|
+
body: JSON.stringify({ ...project, type, runtime_config: config }),
|
|
396
|
+
});
|
|
397
|
+
await db.query(
|
|
398
|
+
"INSERT INTO audit_logs(user_id,project_id,action) VALUES($1,$2,$3)",
|
|
399
|
+
[user.id, id, "project.config"],
|
|
400
|
+
);
|
|
401
|
+
return json({ type, runtime_config: config });
|
|
402
|
+
}
|
|
315
403
|
if (method === "GET" && !action) {
|
|
316
404
|
let current = project;
|
|
317
405
|
try {
|
|
@@ -332,14 +420,6 @@ async function handle(req, { params }) {
|
|
|
332
420
|
if (method === "PUT" && action === "source") {
|
|
333
421
|
if (project.source_type !== "zip")
|
|
334
422
|
return json({ error: "This project uses Git" }, 400);
|
|
335
|
-
const chunks = [];
|
|
336
|
-
let size = 0;
|
|
337
|
-
for await (const chunk of req.body) {
|
|
338
|
-
size += chunk.byteLength;
|
|
339
|
-
if (size > MAX_ZIP_BYTES)
|
|
340
|
-
return json({ error: "ZIP limit is 25 MB" }, 413);
|
|
341
|
-
chunks.push(Buffer.from(chunk));
|
|
342
|
-
}
|
|
343
423
|
await agent("/projects", {
|
|
344
424
|
method: "POST",
|
|
345
425
|
body: JSON.stringify(project),
|
|
@@ -348,7 +428,9 @@ async function handle(req, { params }) {
|
|
|
348
428
|
await agent(`/projects/${id}/source`, {
|
|
349
429
|
method: "PUT",
|
|
350
430
|
headers: { "Content-Type": "application/zip" },
|
|
351
|
-
body:
|
|
431
|
+
body: req.body,
|
|
432
|
+
duplex: "half",
|
|
433
|
+
signal: AbortSignal.timeout(24 * 60 * 60 * 1000),
|
|
352
434
|
}),
|
|
353
435
|
);
|
|
354
436
|
}
|
|
@@ -976,6 +976,84 @@ footer > span:last-child {
|
|
|
976
976
|
color: #227757;
|
|
977
977
|
border: 1px solid #d6eadc;
|
|
978
978
|
}
|
|
979
|
+
.toast-stack {
|
|
980
|
+
position: fixed;
|
|
981
|
+
top: 22px;
|
|
982
|
+
right: 22px;
|
|
983
|
+
z-index: 70;
|
|
984
|
+
display: flex;
|
|
985
|
+
flex-direction: column;
|
|
986
|
+
gap: 10px;
|
|
987
|
+
width: min(380px, calc(100vw - 32px));
|
|
988
|
+
pointer-events: none;
|
|
989
|
+
}
|
|
990
|
+
.toast {
|
|
991
|
+
pointer-events: auto;
|
|
992
|
+
border-radius: 8px;
|
|
993
|
+
border: 1px solid transparent;
|
|
994
|
+
box-shadow: 0 14px 36px #142f3c24;
|
|
995
|
+
overflow: hidden;
|
|
996
|
+
}
|
|
997
|
+
.toast.success {
|
|
998
|
+
background: #eaf7ef;
|
|
999
|
+
color: #1d6b4a;
|
|
1000
|
+
border-color: #d6eadc;
|
|
1001
|
+
}
|
|
1002
|
+
.toast.progress {
|
|
1003
|
+
background: #fff6e8;
|
|
1004
|
+
color: #8a5a12;
|
|
1005
|
+
border-color: #f0e0c2;
|
|
1006
|
+
}
|
|
1007
|
+
.toast.failed {
|
|
1008
|
+
background: #fff0ed;
|
|
1009
|
+
color: #b85243;
|
|
1010
|
+
border-color: #f3d9d4;
|
|
1011
|
+
}
|
|
1012
|
+
.toast-row {
|
|
1013
|
+
display: flex;
|
|
1014
|
+
align-items: flex-start;
|
|
1015
|
+
justify-content: space-between;
|
|
1016
|
+
gap: 12px;
|
|
1017
|
+
padding: 14px 14px 12px;
|
|
1018
|
+
}
|
|
1019
|
+
.toast-row p {
|
|
1020
|
+
margin: 0;
|
|
1021
|
+
font-size: 13px;
|
|
1022
|
+
line-height: 1.5;
|
|
1023
|
+
}
|
|
1024
|
+
.toast-row button {
|
|
1025
|
+
background: transparent;
|
|
1026
|
+
color: inherit;
|
|
1027
|
+
padding: 0;
|
|
1028
|
+
}
|
|
1029
|
+
.toast-track {
|
|
1030
|
+
height: 5px;
|
|
1031
|
+
background: #00000014;
|
|
1032
|
+
}
|
|
1033
|
+
.toast.success .toast-progress {
|
|
1034
|
+
background: #1d6b4a;
|
|
1035
|
+
}
|
|
1036
|
+
.toast.progress .toast-progress {
|
|
1037
|
+
background: #c4842a;
|
|
1038
|
+
}
|
|
1039
|
+
.toast.failed .toast-progress {
|
|
1040
|
+
background: #b85243;
|
|
1041
|
+
}
|
|
1042
|
+
.toast-progress {
|
|
1043
|
+
display: block;
|
|
1044
|
+
height: 100%;
|
|
1045
|
+
width: 100%;
|
|
1046
|
+
transform-origin: left center;
|
|
1047
|
+
animation: toast-countdown 10s linear forwards;
|
|
1048
|
+
}
|
|
1049
|
+
@keyframes toast-countdown {
|
|
1050
|
+
from {
|
|
1051
|
+
transform: scaleX(1);
|
|
1052
|
+
}
|
|
1053
|
+
to {
|
|
1054
|
+
transform: scaleX(0);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
979
1057
|
.dismissible {
|
|
980
1058
|
display: flex;
|
|
981
1059
|
justify-content: space-between;
|
|
@@ -1011,6 +1089,7 @@ footer > span:last-child {
|
|
|
1011
1089
|
}
|
|
1012
1090
|
.tabs {
|
|
1013
1091
|
display: flex;
|
|
1092
|
+
flex-wrap: wrap;
|
|
1014
1093
|
gap: 25px;
|
|
1015
1094
|
border-bottom: 1px solid #dce4e7;
|
|
1016
1095
|
margin-bottom: 22px;
|
|
@@ -1033,7 +1112,7 @@ footer > span:last-child {
|
|
|
1033
1112
|
}
|
|
1034
1113
|
dl {
|
|
1035
1114
|
display: grid;
|
|
1036
|
-
grid-template-columns:
|
|
1115
|
+
grid-template-columns: 140px minmax(0, 1fr);
|
|
1037
1116
|
gap: 19px 15px;
|
|
1038
1117
|
padding: 24px;
|
|
1039
1118
|
margin: 0;
|
|
@@ -1280,6 +1359,35 @@ label small {
|
|
|
1280
1359
|
margin-top: 24px;
|
|
1281
1360
|
padding-top: 20px;
|
|
1282
1361
|
}
|
|
1362
|
+
.alert-backdrop {
|
|
1363
|
+
z-index: 80;
|
|
1364
|
+
}
|
|
1365
|
+
.alert-modal {
|
|
1366
|
+
width: 420px;
|
|
1367
|
+
}
|
|
1368
|
+
.alert-modal h2 {
|
|
1369
|
+
margin-bottom: 8px;
|
|
1370
|
+
}
|
|
1371
|
+
.alert-modal > p {
|
|
1372
|
+
color: var(--muted);
|
|
1373
|
+
line-height: 1.6;
|
|
1374
|
+
margin-bottom: 0;
|
|
1375
|
+
}
|
|
1376
|
+
.alert-confirm {
|
|
1377
|
+
color: white;
|
|
1378
|
+
background: #b65c58;
|
|
1379
|
+
border: 1px solid #b65c58;
|
|
1380
|
+
border-radius: 6px;
|
|
1381
|
+
padding: 11px 16px;
|
|
1382
|
+
min-height: 38px;
|
|
1383
|
+
font-size: 11px;
|
|
1384
|
+
font-weight: 550;
|
|
1385
|
+
}
|
|
1386
|
+
.alert-confirm:focus-visible,
|
|
1387
|
+
.alert-modal .secondary:focus-visible {
|
|
1388
|
+
outline: 2px solid var(--green);
|
|
1389
|
+
outline-offset: 2px;
|
|
1390
|
+
}
|
|
1283
1391
|
.login {
|
|
1284
1392
|
min-height: 100vh;
|
|
1285
1393
|
display: grid;
|
|
@@ -1709,27 +1817,105 @@ html[data-theme="dark"] .danger-text {
|
|
|
1709
1817
|
.database-screen {
|
|
1710
1818
|
margin-bottom: 10px;
|
|
1711
1819
|
}
|
|
1712
|
-
.
|
|
1820
|
+
.native-browser {
|
|
1821
|
+
padding-bottom: 8px;
|
|
1822
|
+
}
|
|
1823
|
+
.native-browser .section-heading {
|
|
1824
|
+
padding: 18px 23px 0;
|
|
1825
|
+
}
|
|
1826
|
+
.db-pills {
|
|
1713
1827
|
display: flex;
|
|
1714
1828
|
flex-wrap: wrap;
|
|
1715
|
-
|
|
1829
|
+
gap: 8px;
|
|
1830
|
+
padding: 16px 23px;
|
|
1831
|
+
}
|
|
1832
|
+
.db-pills button {
|
|
1833
|
+
border: 1px solid #dde4e7;
|
|
1834
|
+
border-radius: 6px;
|
|
1835
|
+
background: transparent;
|
|
1836
|
+
color: #3e5360;
|
|
1837
|
+
font-size: 12px;
|
|
1838
|
+
padding: 7px 10px;
|
|
1839
|
+
}
|
|
1840
|
+
.db-pills button.active {
|
|
1841
|
+
border-color: var(--green);
|
|
1842
|
+
color: var(--green);
|
|
1843
|
+
}
|
|
1844
|
+
.browser-note {
|
|
1845
|
+
padding: 0 23px 16px;
|
|
1846
|
+
}
|
|
1847
|
+
.database-create {
|
|
1848
|
+
display: grid;
|
|
1849
|
+
grid-template-columns: minmax(220px, 1fr) auto;
|
|
1850
|
+
align-items: end;
|
|
1716
1851
|
gap: 12px 16px;
|
|
1717
1852
|
padding: 18px 23px 20px;
|
|
1718
1853
|
}
|
|
1719
1854
|
.database-create label {
|
|
1720
|
-
flex: 1 1 220px;
|
|
1721
|
-
max-width: 460px;
|
|
1722
1855
|
margin-bottom: 0;
|
|
1856
|
+
min-width: 0;
|
|
1723
1857
|
}
|
|
1724
1858
|
.database-create small {
|
|
1725
1859
|
margin-top: 4px;
|
|
1860
|
+
line-height: 1.45;
|
|
1726
1861
|
}
|
|
1727
|
-
.database-create input
|
|
1862
|
+
.database-create input,
|
|
1863
|
+
.database-create select {
|
|
1728
1864
|
height: 40px;
|
|
1729
1865
|
margin-top: 8px;
|
|
1866
|
+
box-sizing: border-box;
|
|
1867
|
+
}
|
|
1868
|
+
.database-create select {
|
|
1869
|
+
display: block;
|
|
1870
|
+
width: 100%;
|
|
1871
|
+
padding: 0 36px 0 12px;
|
|
1872
|
+
border: 1px solid #dde4e7;
|
|
1873
|
+
border-radius: 6px;
|
|
1874
|
+
background-color: var(--surface);
|
|
1875
|
+
color: #3e5360;
|
|
1876
|
+
font-size: 12px;
|
|
1877
|
+
appearance: none;
|
|
1878
|
+
background-image:
|
|
1879
|
+
linear-gradient(45deg, transparent 50%, var(--muted) 50%),
|
|
1880
|
+
linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
|
1881
|
+
background-position:
|
|
1882
|
+
calc(100% - 18px) center,
|
|
1883
|
+
calc(100% - 13px) center;
|
|
1884
|
+
background-size:
|
|
1885
|
+
5px 5px,
|
|
1886
|
+
5px 5px;
|
|
1887
|
+
background-repeat: no-repeat;
|
|
1730
1888
|
}
|
|
1731
1889
|
.database-create .primary {
|
|
1732
1890
|
height: 40px;
|
|
1891
|
+
min-height: 40px;
|
|
1892
|
+
}
|
|
1893
|
+
html[data-theme="dark"] .db-pills button {
|
|
1894
|
+
border-color: #31464c;
|
|
1895
|
+
color: #d5e4ea;
|
|
1896
|
+
}
|
|
1897
|
+
html[data-theme="dark"] .database-create select {
|
|
1898
|
+
background-color: var(--surface);
|
|
1899
|
+
color: var(--ink);
|
|
1900
|
+
border-color: var(--line);
|
|
1901
|
+
background-image:
|
|
1902
|
+
linear-gradient(45deg, transparent 50%, var(--muted) 50%),
|
|
1903
|
+
linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
|
1904
|
+
background-position:
|
|
1905
|
+
calc(100% - 18px) center,
|
|
1906
|
+
calc(100% - 13px) center;
|
|
1907
|
+
background-size:
|
|
1908
|
+
5px 5px,
|
|
1909
|
+
5px 5px;
|
|
1910
|
+
background-repeat: no-repeat;
|
|
1911
|
+
}
|
|
1912
|
+
@media (max-width: 720px) {
|
|
1913
|
+
.database-create {
|
|
1914
|
+
grid-template-columns: 1fr;
|
|
1915
|
+
}
|
|
1916
|
+
.database-create .primary {
|
|
1917
|
+
justify-self: start;
|
|
1918
|
+
}
|
|
1733
1919
|
}
|
|
1734
1920
|
.mail-address {
|
|
1735
1921
|
display: flex;
|
|
@@ -2016,11 +2202,37 @@ html[data-theme="dark"] .badge.queued {
|
|
|
2016
2202
|
background: #3a321c;
|
|
2017
2203
|
color: #efd392;
|
|
2018
2204
|
}
|
|
2019
|
-
html[data-theme="dark"] .notice
|
|
2205
|
+
html[data-theme="dark"] .notice,
|
|
2206
|
+
html[data-theme="dark"] .toast.success {
|
|
2020
2207
|
background: #17352c;
|
|
2021
2208
|
color: #b7efe4;
|
|
2022
2209
|
border-color: #245246;
|
|
2023
2210
|
}
|
|
2211
|
+
html[data-theme="dark"] .toast {
|
|
2212
|
+
box-shadow: 0 14px 36px #00000066;
|
|
2213
|
+
}
|
|
2214
|
+
html[data-theme="dark"] .toast.progress {
|
|
2215
|
+
background: #3a321c;
|
|
2216
|
+
color: #efd392;
|
|
2217
|
+
border-color: #5a4a2e;
|
|
2218
|
+
}
|
|
2219
|
+
html[data-theme="dark"] .toast.failed {
|
|
2220
|
+
background: #3a2422;
|
|
2221
|
+
color: #f0b2a8;
|
|
2222
|
+
border-color: #5a332e;
|
|
2223
|
+
}
|
|
2224
|
+
html[data-theme="dark"] .toast-track {
|
|
2225
|
+
background: #ffffff18;
|
|
2226
|
+
}
|
|
2227
|
+
html[data-theme="dark"] .toast.success .toast-progress {
|
|
2228
|
+
background: #8ee0d0;
|
|
2229
|
+
}
|
|
2230
|
+
html[data-theme="dark"] .toast.progress .toast-progress {
|
|
2231
|
+
background: #efd392;
|
|
2232
|
+
}
|
|
2233
|
+
html[data-theme="dark"] .toast.failed .toast-progress {
|
|
2234
|
+
background: #f0b2a8;
|
|
2235
|
+
}
|
|
2024
2236
|
html[data-theme="dark"] .quick-start {
|
|
2025
2237
|
background: linear-gradient(110deg, #17342e, #172126);
|
|
2026
2238
|
}
|
|
@@ -2055,3 +2267,97 @@ html[data-theme="dark"] .stat,
|
|
|
2055
2267
|
html[data-theme="dark"] .panel {
|
|
2056
2268
|
border-color: var(--line);
|
|
2057
2269
|
}
|
|
2270
|
+
.file-crumbs,
|
|
2271
|
+
.file-actions,
|
|
2272
|
+
.env-row,
|
|
2273
|
+
.app-fields {
|
|
2274
|
+
display: flex;
|
|
2275
|
+
flex-wrap: wrap;
|
|
2276
|
+
gap: 8px;
|
|
2277
|
+
align-items: center;
|
|
2278
|
+
margin: 12px 0;
|
|
2279
|
+
}
|
|
2280
|
+
.file-crumbs button,
|
|
2281
|
+
.file-row > button,
|
|
2282
|
+
.file-row-actions button,
|
|
2283
|
+
.file-row-actions a {
|
|
2284
|
+
background: transparent;
|
|
2285
|
+
color: var(--ink);
|
|
2286
|
+
padding: 0;
|
|
2287
|
+
}
|
|
2288
|
+
.file-crumbs button + button::before {
|
|
2289
|
+
content: "/";
|
|
2290
|
+
margin-right: 8px;
|
|
2291
|
+
color: var(--muted);
|
|
2292
|
+
}
|
|
2293
|
+
.file-list {
|
|
2294
|
+
display: grid;
|
|
2295
|
+
gap: 6px;
|
|
2296
|
+
}
|
|
2297
|
+
.file-row {
|
|
2298
|
+
display: flex;
|
|
2299
|
+
justify-content: space-between;
|
|
2300
|
+
align-items: center;
|
|
2301
|
+
gap: 12px;
|
|
2302
|
+
padding: 8px 10px;
|
|
2303
|
+
border: 1px solid var(--line);
|
|
2304
|
+
border-radius: 8px;
|
|
2305
|
+
background: var(--surface);
|
|
2306
|
+
}
|
|
2307
|
+
.file-row > button {
|
|
2308
|
+
display: flex;
|
|
2309
|
+
justify-content: space-between;
|
|
2310
|
+
flex: 1;
|
|
2311
|
+
text-align: left;
|
|
2312
|
+
gap: 12px;
|
|
2313
|
+
}
|
|
2314
|
+
.file-row small {
|
|
2315
|
+
color: var(--muted);
|
|
2316
|
+
}
|
|
2317
|
+
.file-row-actions {
|
|
2318
|
+
display: flex;
|
|
2319
|
+
gap: 10px;
|
|
2320
|
+
align-items: center;
|
|
2321
|
+
}
|
|
2322
|
+
.file-upload input {
|
|
2323
|
+
display: none;
|
|
2324
|
+
}
|
|
2325
|
+
.file-editor {
|
|
2326
|
+
width: 100%;
|
|
2327
|
+
min-height: 280px;
|
|
2328
|
+
margin: 8px 0 12px;
|
|
2329
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
2330
|
+
font-size: 13px;
|
|
2331
|
+
}
|
|
2332
|
+
.app-fields {
|
|
2333
|
+
align-items: end;
|
|
2334
|
+
}
|
|
2335
|
+
.app-fields label {
|
|
2336
|
+
display: grid;
|
|
2337
|
+
gap: 6px;
|
|
2338
|
+
min-width: 180px;
|
|
2339
|
+
font-size: 13px;
|
|
2340
|
+
}
|
|
2341
|
+
.env-row input {
|
|
2342
|
+
min-width: 140px;
|
|
2343
|
+
}
|
|
2344
|
+
.connection-box,
|
|
2345
|
+
.db-mini {
|
|
2346
|
+
margin: 14px 0;
|
|
2347
|
+
}
|
|
2348
|
+
.connection-box code {
|
|
2349
|
+
display: block;
|
|
2350
|
+
margin: 8px 0;
|
|
2351
|
+
overflow-wrap: anywhere;
|
|
2352
|
+
}
|
|
2353
|
+
.db-mini {
|
|
2354
|
+
list-style: none;
|
|
2355
|
+
padding: 0;
|
|
2356
|
+
}
|
|
2357
|
+
.db-mini li {
|
|
2358
|
+
display: flex;
|
|
2359
|
+
justify-content: space-between;
|
|
2360
|
+
gap: 12px;
|
|
2361
|
+
padding: 8px 0;
|
|
2362
|
+
border-bottom: 1px solid var(--line);
|
|
2363
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./globals.css";
|
|
2
2
|
import { ThemeSync } from "./ui/theme";
|
|
3
|
+
import { ToastProvider } from "./ui/toast";
|
|
3
4
|
export const metadata = {
|
|
4
5
|
title: "Naviyra · Cloudflare hosting",
|
|
5
6
|
description: "Your applications. Your infrastructure. One panel.",
|
|
@@ -11,7 +12,7 @@ export default function Layout({ children }) {
|
|
|
11
12
|
<body>
|
|
12
13
|
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
|
13
14
|
<ThemeSync />
|
|
14
|
-
{children}
|
|
15
|
+
<ToastProvider>{children}</ToastProvider>
|
|
15
16
|
</body>
|
|
16
17
|
</html>
|
|
17
18
|
);
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { ArrowRight, ShieldCheck, Cloud, Terminal } from "lucide-react";
|
|
4
4
|
import { ThemeToggle } from "../ui/theme";
|
|
5
|
+
import { useToast } from "../ui/toast";
|
|
5
6
|
export default function Login() {
|
|
6
|
-
const
|
|
7
|
+
const toast = useToast();
|
|
7
8
|
const [busy, setBusy] = useState(false);
|
|
8
9
|
const [ready, setReady] = useState(false);
|
|
9
10
|
useEffect(() => setReady(true), []);
|
|
10
11
|
async function submit(event) {
|
|
11
12
|
event.preventDefault();
|
|
12
13
|
setBusy(true);
|
|
13
|
-
setError("");
|
|
14
14
|
const values = Object.fromEntries(new FormData(event.currentTarget));
|
|
15
15
|
try {
|
|
16
16
|
const response = await fetch("/api/auth/login", {
|
|
@@ -22,7 +22,7 @@ export default function Login() {
|
|
|
22
22
|
if (!response.ok) throw Error(body.error);
|
|
23
23
|
window.location.href = "/";
|
|
24
24
|
} catch (error) {
|
|
25
|
-
|
|
25
|
+
toast.failed(error.message);
|
|
26
26
|
setBusy(false);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -86,11 +86,6 @@ export default function Login() {
|
|
|
86
86
|
required
|
|
87
87
|
/>
|
|
88
88
|
</label>
|
|
89
|
-
{error && (
|
|
90
|
-
<div role="alert" className="error">
|
|
91
|
-
{error}
|
|
92
|
-
</div>
|
|
93
|
-
)}
|
|
94
89
|
<button className="primary" disabled={busy || !ready}>
|
|
95
90
|
{busy ? "Signing in…" : "Sign in"}
|
|
96
91
|
<ArrowRight size={17} />
|
package/apps/panel/app/page.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { redirect } from "next/navigation";
|
|
2
2
|
import { session } from "../lib/auth.mjs";
|
|
3
3
|
import Dashboard from "./ui/dashboard";
|
|
4
|
+
import { ConfirmProvider } from "./ui/confirm";
|
|
4
5
|
export const dynamic = "force-dynamic";
|
|
5
6
|
const views = new Set([
|
|
6
7
|
"projects",
|
|
@@ -10,7 +11,14 @@ const views = new Set([
|
|
|
10
11
|
"dns",
|
|
11
12
|
"email",
|
|
12
13
|
]);
|
|
13
|
-
const tabs = new Set([
|
|
14
|
+
const tabs = new Set([
|
|
15
|
+
"overview",
|
|
16
|
+
"files",
|
|
17
|
+
"application",
|
|
18
|
+
"database",
|
|
19
|
+
"logs",
|
|
20
|
+
"deployments",
|
|
21
|
+
]);
|
|
14
22
|
const projectId = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15
23
|
function one(value) {
|
|
16
24
|
return (Array.isArray(value) ? value[0] : value) || "";
|
|
@@ -23,11 +31,13 @@ export default async function Page({ searchParams }) {
|
|
|
23
31
|
const project = projectId.test(one(params?.project)) ? one(params.project) : null;
|
|
24
32
|
const tab = tabs.has(one(params?.tab)) ? one(params.tab) : "overview";
|
|
25
33
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
<ConfirmProvider>
|
|
35
|
+
<Dashboard
|
|
36
|
+
user={user}
|
|
37
|
+
initialView={view}
|
|
38
|
+
initialProject={project}
|
|
39
|
+
initialTab={tab}
|
|
40
|
+
/>
|
|
41
|
+
</ConfirmProvider>
|
|
32
42
|
);
|
|
33
43
|
}
|