@naviyra/cloud-panel 0.0.3-beta2 → 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 +1 -0
- 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 +94 -13
- package/apps/panel/app/globals.css +262 -3
- 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 -29
- 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
|
@@ -5,23 +5,25 @@ import { db } from "../../../lib/db.mjs";
|
|
|
5
5
|
import { session, signIn } from "../../../lib/auth.mjs";
|
|
6
6
|
import { originAllowed } from "../../../lib/request-origin.mjs";
|
|
7
7
|
import { agent, reconcile } from "../../../lib/agent.mjs";
|
|
8
|
-
import { projectInput,
|
|
8
|
+
import { projectInput, runtimeConfig, idSchema } from "@naviyra/shared";
|
|
9
9
|
import {
|
|
10
10
|
listDatabases,
|
|
11
11
|
createDatabase,
|
|
12
12
|
deleteDatabase,
|
|
13
13
|
resetDatabasePassword,
|
|
14
|
+
listNativeDatabases,
|
|
15
|
+
browseNativeDatabase,
|
|
14
16
|
} from "../../../lib/databases.mjs";
|
|
15
17
|
export const runtime = "nodejs";
|
|
16
18
|
export const dynamic = "force-dynamic";
|
|
17
19
|
const attempts = (globalThis.naviyraAttempts ||= new Map());
|
|
18
20
|
const json = (data, status = 200) =>
|
|
19
21
|
Response.json(data, { status, headers: { "Cache-Control": "no-store" } });
|
|
20
|
-
async function readJson(req) {
|
|
21
|
-
if (Number(req.headers.get("content-length")) >
|
|
22
|
+
async function readJson(req, limit = 8192) {
|
|
23
|
+
if (Number(req.headers.get("content-length")) > limit)
|
|
22
24
|
throw Object.assign(Error("Request too large"), { status: 413 });
|
|
23
25
|
const text = await req.text();
|
|
24
|
-
if (text.length >
|
|
26
|
+
if (text.length > limit)
|
|
25
27
|
throw Object.assign(Error("Request too large"), { status: 413 });
|
|
26
28
|
return JSON.parse(text);
|
|
27
29
|
}
|
|
@@ -91,6 +93,15 @@ async function handle(req, { params }) {
|
|
|
91
93
|
}
|
|
92
94
|
if (route === "databases" && method === "GET")
|
|
93
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
|
+
}
|
|
94
105
|
if (route === "databases" && method === "POST") {
|
|
95
106
|
const body = await readJson(req);
|
|
96
107
|
const created = await createDatabase(
|
|
@@ -313,6 +324,82 @@ async function handle(req, { params }) {
|
|
|
313
324
|
])
|
|
314
325
|
).rows[0];
|
|
315
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
|
+
}
|
|
316
403
|
if (method === "GET" && !action) {
|
|
317
404
|
let current = project;
|
|
318
405
|
try {
|
|
@@ -333,14 +420,6 @@ async function handle(req, { params }) {
|
|
|
333
420
|
if (method === "PUT" && action === "source") {
|
|
334
421
|
if (project.source_type !== "zip")
|
|
335
422
|
return json({ error: "This project uses Git" }, 400);
|
|
336
|
-
const chunks = [];
|
|
337
|
-
let size = 0;
|
|
338
|
-
for await (const chunk of req.body) {
|
|
339
|
-
size += chunk.byteLength;
|
|
340
|
-
if (size > MAX_ZIP_BYTES)
|
|
341
|
-
return json({ error: "ZIP limit is 25 MB" }, 413);
|
|
342
|
-
chunks.push(Buffer.from(chunk));
|
|
343
|
-
}
|
|
344
423
|
await agent("/projects", {
|
|
345
424
|
method: "POST",
|
|
346
425
|
body: JSON.stringify(project),
|
|
@@ -349,7 +428,9 @@ async function handle(req, { params }) {
|
|
|
349
428
|
await agent(`/projects/${id}/source`, {
|
|
350
429
|
method: "PUT",
|
|
351
430
|
headers: { "Content-Type": "application/zip" },
|
|
352
|
-
body:
|
|
431
|
+
body: req.body,
|
|
432
|
+
duplex: "half",
|
|
433
|
+
signal: AbortSignal.timeout(24 * 60 * 60 * 1000),
|
|
353
434
|
}),
|
|
354
435
|
);
|
|
355
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,9 +1817,36 @@ html[data-theme="dark"] .danger-text {
|
|
|
1709
1817
|
.database-screen {
|
|
1710
1818
|
margin-bottom: 10px;
|
|
1711
1819
|
}
|
|
1820
|
+
.native-browser {
|
|
1821
|
+
padding-bottom: 8px;
|
|
1822
|
+
}
|
|
1823
|
+
.native-browser .section-heading {
|
|
1824
|
+
padding: 18px 23px 0;
|
|
1825
|
+
}
|
|
1826
|
+
.db-pills {
|
|
1827
|
+
display: flex;
|
|
1828
|
+
flex-wrap: wrap;
|
|
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
|
+
}
|
|
1712
1847
|
.database-create {
|
|
1713
1848
|
display: grid;
|
|
1714
|
-
grid-template-columns: minmax(
|
|
1849
|
+
grid-template-columns: minmax(220px, 1fr) auto;
|
|
1715
1850
|
align-items: end;
|
|
1716
1851
|
gap: 12px 16px;
|
|
1717
1852
|
padding: 18px 23px 20px;
|
|
@@ -1755,6 +1890,10 @@ html[data-theme="dark"] .danger-text {
|
|
|
1755
1890
|
height: 40px;
|
|
1756
1891
|
min-height: 40px;
|
|
1757
1892
|
}
|
|
1893
|
+
html[data-theme="dark"] .db-pills button {
|
|
1894
|
+
border-color: #31464c;
|
|
1895
|
+
color: #d5e4ea;
|
|
1896
|
+
}
|
|
1758
1897
|
html[data-theme="dark"] .database-create select {
|
|
1759
1898
|
background-color: var(--surface);
|
|
1760
1899
|
color: var(--ink);
|
|
@@ -2063,11 +2202,37 @@ html[data-theme="dark"] .badge.queued {
|
|
|
2063
2202
|
background: #3a321c;
|
|
2064
2203
|
color: #efd392;
|
|
2065
2204
|
}
|
|
2066
|
-
html[data-theme="dark"] .notice
|
|
2205
|
+
html[data-theme="dark"] .notice,
|
|
2206
|
+
html[data-theme="dark"] .toast.success {
|
|
2067
2207
|
background: #17352c;
|
|
2068
2208
|
color: #b7efe4;
|
|
2069
2209
|
border-color: #245246;
|
|
2070
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
|
+
}
|
|
2071
2236
|
html[data-theme="dark"] .quick-start {
|
|
2072
2237
|
background: linear-gradient(110deg, #17342e, #172126);
|
|
2073
2238
|
}
|
|
@@ -2102,3 +2267,97 @@ html[data-theme="dark"] .stat,
|
|
|
2102
2267
|
html[data-theme="dark"] .panel {
|
|
2103
2268
|
border-color: var(--line);
|
|
2104
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
|
}
|