@arch-cadre/panel 1.0.7 → 1.0.10

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.
Files changed (53) hide show
  1. package/dist/ui/activity-log/pages/log-list.cjs +2 -2
  2. package/dist/ui/components/profile/components.cjs +2 -2
  3. package/dist/ui/components/profile/components.d.ts +1 -1
  4. package/dist/ui/components/profile/components.mjs +1 -1
  5. package/dist/ui/rbac/pages/rbac-admin.cjs +14 -14
  6. package/dist/ui/session-manager/components/sessions-list.cjs +5 -5
  7. package/dist/ui/session-manager/components/sessions-list.mjs +1 -1
  8. package/dist/ui/session-manager/pages/sessions-page.cjs +3 -3
  9. package/dist/ui/session-manager/pages/sessions-page.mjs +1 -1
  10. package/package.json +7 -6
  11. package/src/actions/actions.ts +17 -0
  12. package/src/actions/activity-log/index.ts +17 -0
  13. package/src/actions/index.ts +2 -0
  14. package/src/actions/manager.ts +168 -0
  15. package/src/actions/profile.ts +173 -0
  16. package/src/actions/rbac/index.ts +131 -0
  17. package/src/actions/session-manager/index.ts +87 -0
  18. package/src/actions/settings.ts +34 -0
  19. package/src/index.ts +135 -0
  20. package/src/intl.d.ts +9 -0
  21. package/src/navigation.ts +57 -0
  22. package/src/routes.ts +107 -0
  23. package/src/schema/activity-log.ts +16 -0
  24. package/src/schema.ts +1 -0
  25. package/src/types.ts +18 -0
  26. package/src/ui/activity-log/components/ActivityStatsWidget.tsx +37 -0
  27. package/src/ui/activity-log/components/RecentLogsWidget.tsx +74 -0
  28. package/src/ui/activity-log/pages/log-list.tsx +91 -0
  29. package/src/ui/components/app-content.tsx +51 -0
  30. package/src/ui/components/app-header.tsx +65 -0
  31. package/src/ui/components/app-sidebar.tsx +249 -0
  32. package/src/ui/components/app-user.tsx +126 -0
  33. package/src/ui/components/breadcrumb-slot.tsx +52 -0
  34. package/src/ui/components/manager/module-card.tsx +327 -0
  35. package/src/ui/components/manager/module-list.tsx +59 -0
  36. package/src/ui/components/manager/module-upload.tsx +84 -0
  37. package/src/ui/components/profile/components.tsx +311 -0
  38. package/src/ui/components/profile/link.tsx +36 -0
  39. package/src/ui/components/profile/page.tsx +45 -0
  40. package/src/ui/components/sidebar-slot.tsx +47 -0
  41. package/src/ui/dashboard/page.tsx +17 -0
  42. package/src/ui/dashboard/widgets/WelcomeBackUserWidget.tsx +47 -0
  43. package/src/ui/error.tsx +82 -0
  44. package/src/ui/layout.tsx +54 -0
  45. package/src/ui/modules/docs/page.tsx +105 -0
  46. package/src/ui/modules/page.tsx +30 -0
  47. package/src/ui/page.tsx +15 -0
  48. package/src/ui/rbac/pages/rbac-admin.tsx +551 -0
  49. package/src/ui/router.tsx +69 -0
  50. package/src/ui/session-manager/components/sessions-list.tsx +303 -0
  51. package/src/ui/session-manager/pages/sessions-page.tsx +22 -0
  52. package/src/ui/settings/page.tsx +73 -0
  53. package/src/ui/settings-page.tsx +97 -0
@@ -8,11 +8,11 @@ var _server = require("@arch-cadre/intl/server");
8
8
  var _ui = require("@arch-cadre/ui");
9
9
  var _table = require("@arch-cadre/ui/components/table");
10
10
  var React = _interopRequireWildcard(require("react"));
11
- var _activityLog = require("../../../actions/activity-log/index.cjs");
11
+ var _index = require("../../../actions/activity-log/index.cjs");
12
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
13
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
14
  async function ActivityLogPage() {
15
- const logs = await (0, _activityLog.getActivityLogs)();
15
+ const logs = await (0, _index.getActivityLogs)();
16
16
  const {
17
17
  t
18
18
  } = await (0, _server.getTranslation)();
@@ -7,14 +7,14 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.UpdateEmailForm = UpdateEmailForm;
8
8
  exports.UpdatePasswordForm = UpdatePasswordForm;
9
9
  exports.UpdateProfileForm = UpdateProfileForm;
10
- var _react = _interopRequireWildcard(require("react"));
11
- var React = _react;
12
10
  var _intl = require("@arch-cadre/intl");
13
11
  var _ui = require("@arch-cadre/ui");
14
12
  var _avatar = require("@arch-cadre/ui/components/avatar");
15
13
  var _button = require("@arch-cadre/ui/components/button");
16
14
  var _input = require("@arch-cadre/ui/components/input");
17
15
  var _label = require("@arch-cadre/ui/components/label");
16
+ var _react = _interopRequireWildcard(require("react"));
17
+ var React = _react;
18
18
  var _profile = require("../../../actions/profile.cjs");
19
19
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
20
20
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import type { FullUser } from "@arch-cadre/core";
2
+ import * as React from "react";
3
3
  export declare function UpdateProfileForm({ user }: {
4
4
  user: FullUser;
5
5
  }): React.JSX.Element;
@@ -1,5 +1,4 @@
1
1
  "use client";
2
- import * as React from "react";
3
2
  import { useTranslation } from "@arch-cadre/intl";
4
3
  import { cn, Icon, toast } from "@arch-cadre/ui";
5
4
  import {
@@ -10,6 +9,7 @@ import {
10
9
  import { Button } from "@arch-cadre/ui/components/button";
11
10
  import { Input } from "@arch-cadre/ui/components/input";
12
11
  import { Label } from "@arch-cadre/ui/components/label";
12
+ import * as React from "react";
13
13
  import { useActionState, useRef, useState, useTransition } from "react";
14
14
  import {
15
15
  updateEmailAction,
@@ -14,7 +14,7 @@ var _input = require("@arch-cadre/ui/components/input");
14
14
  var _table = require("@arch-cadre/ui/components/table");
15
15
  var _react = _interopRequireWildcard(require("react"));
16
16
  var React = _react;
17
- var _rbac = require("../../../actions/rbac/index.cjs");
17
+ var _index = require("../../../actions/rbac/index.cjs");
18
18
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
19
19
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
20
20
  function RbacAdminPage() {
@@ -38,7 +38,7 @@ function RbacAdminPage() {
38
38
  const loadData = (0, _react.useCallback)(async () => {
39
39
  setLoading(true);
40
40
  try {
41
- const [r, p, u] = await Promise.all([(0, _rbac.getRoles)(), (0, _rbac.getPermissions)(), (0, _rbac.getUsers)()]);
41
+ const [r, p, u] = await Promise.all([(0, _index.getRoles)(), (0, _index.getPermissions)(), (0, _index.getUsers)()]);
42
42
  setRoles(r);
43
43
  setPermissions(p);
44
44
  setUsers(u);
@@ -51,7 +51,7 @@ function RbacAdminPage() {
51
51
  }, []);
52
52
  const loadRolePermissions = (0, _react.useCallback)(async roleId => {
53
53
  try {
54
- const p = await (0, _rbac.getRolePermissions)(roleId);
54
+ const p = await (0, _index.getRolePermissions)(roleId);
55
55
  setRolePermissions(p);
56
56
  } catch (e) {
57
57
  console.error(`[RBAC] Failed to load permissions for role ${roleId}:`, e);
@@ -60,7 +60,7 @@ function RbacAdminPage() {
60
60
  }, []);
61
61
  const loadUserRbacData = (0, _react.useCallback)(async userId => {
62
62
  try {
63
- const data = await (0, _rbac.getUserRbacData)(userId);
63
+ const data = await (0, _index.getUserRbacData)(userId);
64
64
  setUserRbacData(data);
65
65
  } catch (e) {
66
66
  console.error(`[RBAC] Failed to load RBAC data for user ${userId}:`, e);
@@ -83,7 +83,7 @@ function RbacAdminPage() {
83
83
  const handleCreateRole = (0, _react.useCallback)(async () => {
84
84
  if (!newRoleName) return;
85
85
  try {
86
- await (0, _rbac.createRole)(newRoleName);
86
+ await (0, _index.createRole)(newRoleName);
87
87
  setNewRoleName("");
88
88
  _ui.toast.success(t("Role created."));
89
89
  loadData();
@@ -94,7 +94,7 @@ function RbacAdminPage() {
94
94
  const handleCreatePermission = (0, _react.useCallback)(async () => {
95
95
  if (!newPermissionName) return;
96
96
  try {
97
- await (0, _rbac.createPermission)(newPermissionName);
97
+ await (0, _index.createPermission)(newPermissionName);
98
98
  setNewPermissionName("");
99
99
  _ui.toast.success(t("Permission created."));
100
100
  loadData();
@@ -104,7 +104,7 @@ function RbacAdminPage() {
104
104
  }, [newPermissionName, loadData]);
105
105
  const handleDeleteRole = (0, _react.useCallback)(async id => {
106
106
  try {
107
- await (0, _rbac.deleteRole)(id);
107
+ await (0, _index.deleteRole)(id);
108
108
  _ui.toast.success(t("Role deleted."));
109
109
  if (selectedRole?.id === id) setSelectedRole(null);
110
110
  loadData();
@@ -114,7 +114,7 @@ function RbacAdminPage() {
114
114
  }, [selectedRole, loadData]);
115
115
  const handleDeletePermission = (0, _react.useCallback)(async id => {
116
116
  try {
117
- await (0, _rbac.deletePermission)(id);
117
+ await (0, _index.deletePermission)(id);
118
118
  _ui.toast.success(t("Permission deleted."));
119
119
  loadData();
120
120
  } catch (_e) {
@@ -125,9 +125,9 @@ function RbacAdminPage() {
125
125
  if (!selectedRole) return;
126
126
  try {
127
127
  if (hasIt) {
128
- await (0, _rbac.revokePermissionFromRole)(selectedRole.id, permId);
128
+ await (0, _index.revokePermissionFromRole)(selectedRole.id, permId);
129
129
  } else {
130
- await (0, _rbac.assignPermissionToRole)(selectedRole.id, permId);
130
+ await (0, _index.assignPermissionToRole)(selectedRole.id, permId);
131
131
  }
132
132
  loadRolePermissions(selectedRole.id);
133
133
  } catch (_e) {
@@ -138,9 +138,9 @@ function RbacAdminPage() {
138
138
  if (!selectedUser) return;
139
139
  try {
140
140
  if (hasIt) {
141
- await (0, _rbac.revokeRoleFromUser)(selectedUser.id, roleId);
141
+ await (0, _index.revokeRoleFromUser)(selectedUser.id, roleId);
142
142
  } else {
143
- await (0, _rbac.assignRoleToUser)(selectedUser.id, roleId);
143
+ await (0, _index.assignRoleToUser)(selectedUser.id, roleId);
144
144
  }
145
145
  loadUserRbacData(selectedUser.id);
146
146
  } catch (_e) {
@@ -151,9 +151,9 @@ function RbacAdminPage() {
151
151
  if (!selectedUser) return;
152
152
  try {
153
153
  if (hasIt) {
154
- await (0, _rbac.revokePermissionFromUser)(selectedUser.id, permId);
154
+ await (0, _index.revokePermissionFromUser)(selectedUser.id, permId);
155
155
  } else {
156
- await (0, _rbac.assignPermissionToUser)(selectedUser.id, permId);
156
+ await (0, _index.assignPermissionToUser)(selectedUser.id, permId);
157
157
  }
158
158
  loadUserRbacData(selectedUser.id);
159
159
  } catch (_e) {
@@ -5,15 +5,15 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.SessionsList = SessionsList;
8
- var _react = _interopRequireWildcard(require("react"));
9
- var React = _react;
10
8
  var _intl = require("@arch-cadre/intl");
11
9
  var _ui = require("@arch-cadre/ui");
12
10
  var _alertDialog = require("@arch-cadre/ui/components/alert-dialog");
13
11
  var _badge = require("@arch-cadre/ui/components/badge");
14
12
  var _button = require("@arch-cadre/ui/components/button");
15
13
  var _navigation = require("next/navigation");
16
- var _sessionManager = require("../../../actions/session-manager/index.cjs");
14
+ var _react = _interopRequireWildcard(require("react"));
15
+ var React = _react;
16
+ var _index = require("../../../actions/session-manager/index.cjs");
17
17
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
18
18
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
19
19
  const CORE_SESSION_KEYS = ["id", "createdAt", "expiresAt", "isCurrent", "userId"];
@@ -49,7 +49,7 @@ function SessionsList({
49
49
  const handleRevokeSession = sessionId => {
50
50
  setRevokingSessionId(sessionId);
51
51
  startTransition(async () => {
52
- const result = await (0, _sessionManager.revokeSessionAction)(sessionId);
52
+ const result = await (0, _index.revokeSessionAction)(sessionId);
53
53
  if (result.error) {
54
54
  _ui.toast.error(result.error);
55
55
  } else {
@@ -61,7 +61,7 @@ function SessionsList({
61
61
  };
62
62
  const handleRevokeAllOther = () => {
63
63
  startTransition(async () => {
64
- const result = await (0, _sessionManager.revokeAllOtherSessionsAction)();
64
+ const result = await (0, _index.revokeAllOtherSessionsAction)();
65
65
  if (result.error) {
66
66
  _ui.toast.error(result.error);
67
67
  } else {
@@ -1,5 +1,4 @@
1
1
  "use client";
2
- import * as React from "react";
3
2
  import { useTranslation } from "@arch-cadre/intl";
4
3
  import { cn, Icon, toast } from "@arch-cadre/ui";
5
4
  import {
@@ -16,6 +15,7 @@ import {
16
15
  import { Badge } from "@arch-cadre/ui/components/badge";
17
16
  import { Button } from "@arch-cadre/ui/components/button";
18
17
  import { useRouter } from "next/navigation";
18
+ import * as React from "react";
19
19
  import { useState, useTransition } from "react";
20
20
  import {
21
21
  revokeAllOtherSessionsAction,
@@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  module.exports = SessionsSettingsPage;
7
- var React = _interopRequireWildcard(require("react"));
8
7
  var _server = require("@arch-cadre/intl/server");
9
- var _sessionManager = require("../../../actions/session-manager/index.cjs");
8
+ var React = _interopRequireWildcard(require("react"));
9
+ var _index = require("../../../actions/session-manager/index.cjs");
10
10
  var _sessionsList = require("../components/sessions-list.cjs");
11
11
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
12
12
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -16,7 +16,7 @@ async function SessionsSettingsPage() {
16
16
  } = await (0, _server.getTranslation)();
17
17
  const {
18
18
  sessions
19
- } = await (0, _sessionManager.getSessionsAction)();
19
+ } = await (0, _index.getSessionsAction)();
20
20
  return /* @__PURE__ */React.createElement("div", {
21
21
  className: "space-y-6"
22
22
  }, /* @__PURE__ */React.createElement("div", null, /* @__PURE__ */React.createElement("h2", {
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import { getTranslation } from "@arch-cadre/intl/server";
2
+ import * as React from "react";
3
3
  import { getSessionsAction } from "../../../actions/session-manager/index.mjs";
4
4
  import { SessionsList } from "../components/sessions-list.mjs";
5
5
  export default async function SessionsSettingsPage() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arch-cadre/panel",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "Panel module for Kryo framework",
6
6
  "exports": {
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "files": [
25
25
  "dist",
26
+ "src",
26
27
  "locales",
27
28
  "manifest.json"
28
29
  ],
@@ -36,7 +37,7 @@
36
37
  "lint": "biome check --write"
37
38
  },
38
39
  "dependencies": {
39
- "@arch-cadre/modules": "^0.0.73",
40
+ "@arch-cadre/modules": "^0.0.80",
40
41
  "@hookform/resolvers": "^3.10.0",
41
42
  "react-hook-form": "^7.54.2",
42
43
  "@iconify-json/solar": "^1.2.2",
@@ -48,7 +49,7 @@
48
49
  "zod": "^3.24.1"
49
50
  },
50
51
  "devDependencies": {
51
- "@arch-cadre/core": "^0.0.47",
52
+ "@arch-cadre/core": "^0.0.54",
52
53
  "@types/adm-zip": "^0.5.7",
53
54
  "@types/node": "^20.19.9",
54
55
  "@types/react": "^19",
@@ -57,9 +58,9 @@
57
58
  "unbuild": "^3.6.1"
58
59
  },
59
60
  "peerDependencies": {
60
- "@arch-cadre/core": "^0.0.47",
61
- "@arch-cadre/intl": "^0.0.47",
62
- "@arch-cadre/ui": "^0.0.47",
61
+ "@arch-cadre/core": "^0.0.54",
62
+ "@arch-cadre/intl": "^0.0.54",
63
+ "@arch-cadre/ui": "^0.0.54",
63
64
  "next": ">=13.0.0",
64
65
  "react": "^19.0.0",
65
66
  "react-dom": "^19.0.0"
@@ -0,0 +1,17 @@
1
+ "use server";
2
+
3
+ import { signOut as signOutAction } from "@arch-cadre/core/server";
4
+ import { getTranslation } from "@arch-cadre/intl/server";
5
+ import { revalidatePath } from "next/cache";
6
+
7
+ /**
8
+ * Modern Logout Action delegating to Core Auth
9
+ */
10
+ export async function logoutAction() {
11
+ await signOutAction();
12
+ const { t } = await getTranslation();
13
+
14
+ t("Pos");
15
+
16
+ revalidatePath("/", "layout");
17
+ }
@@ -0,0 +1,17 @@
1
+ "use server";
2
+
3
+ import { db, userTable } from "@arch-cadre/core/server";
4
+ import { desc, eq } from "drizzle-orm";
5
+ import { activityLogsTable } from "../../schema/activity-log";
6
+
7
+ export async function getActivityLogs() {
8
+ return await db
9
+ .select({
10
+ log: activityLogsTable,
11
+ user: { name: userTable.name, email: userTable.email },
12
+ })
13
+ .from(activityLogsTable)
14
+ .leftJoin(userTable, eq(activityLogsTable.userId, userTable.id))
15
+ .orderBy(desc(activityLogsTable.createdAt))
16
+ .limit(100);
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./actions";
2
+ export * from "./settings";
@@ -0,0 +1,168 @@
1
+ "use server";
2
+
3
+ import { exec } from "node:child_process";
4
+ import fs, { constants } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import {
8
+ db,
9
+ eventBus,
10
+ getModulesDir,
11
+ systemModulesTable,
12
+ } from "@arch-cadre/core/server";
13
+ import {
14
+ getModuleStatus,
15
+ getModules,
16
+ ModuleManifestSchema,
17
+ toggleModuleState,
18
+ } from "@arch-cadre/modules/server";
19
+ import AdmZip from "adm-zip";
20
+ import { revalidatePath, unstable_noStore } from "next/cache";
21
+
22
+ export async function toggleModuleAction(moduleId: string, isEnabled: boolean) {
23
+ try {
24
+ console.log(
25
+ `[Module:ModuleManager] Toggle request for ${moduleId} -> ${isEnabled}`,
26
+ );
27
+ const result = await toggleModuleState(moduleId, isEnabled);
28
+ await eventBus.publish("system:module:toggle", { moduleId, isEnabled });
29
+ revalidatePath("/admin/modules");
30
+ revalidatePath("/module/module-manager");
31
+ return result;
32
+ } catch (error) {
33
+ console.error(
34
+ `[Module:ModuleManager] Error toggling module ${moduleId}:`,
35
+ error,
36
+ );
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ export async function uploadModuleAction(formData: FormData) {
42
+ try {
43
+ const file = formData.get("file") as File;
44
+ if (!file) throw new Error("No file provided");
45
+
46
+ const buffer = Buffer.from(await file.arrayBuffer());
47
+ const zip = new AdmZip(buffer);
48
+ const zipEntries = zip.getEntries();
49
+
50
+ const manifestEntry = zipEntries.find((e) =>
51
+ e.entryName.endsWith("manifest.json"),
52
+ );
53
+ if (!manifestEntry) throw new Error("ZIP must contain manifest.json");
54
+
55
+ const moduleRootInZip = manifestEntry.entryName.replace(
56
+ "manifest.json",
57
+ "",
58
+ );
59
+
60
+ const manifestRaw = JSON.parse(manifestEntry.getData().toString("utf8"));
61
+ const manifest = ModuleManifestSchema.parse(manifestRaw);
62
+
63
+ const modulesDir = await getModulesDir();
64
+ const targetDir = path.join(modulesDir, manifest.id);
65
+
66
+ try {
67
+ await fs.access(targetDir);
68
+ throw new Error(`Module with ID "${manifest.id}" already exists.`);
69
+ } catch (e: any) {
70
+ if (e.message.includes("already exists")) throw e;
71
+ }
72
+
73
+ await fs.mkdir(targetDir, { recursive: true });
74
+
75
+ for (const entry of zipEntries) {
76
+ if (entry.entryName.startsWith(moduleRootInZip) && !entry.isDirectory) {
77
+ const relativePath = entry.entryName.slice(moduleRootInZip.length);
78
+ const fullPath = path.join(targetDir, relativePath);
79
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
80
+ await fs.writeFile(fullPath, entry.getData());
81
+ }
82
+ }
83
+
84
+ await db
85
+ .insert(systemModulesTable)
86
+ .values({
87
+ id: manifest.id,
88
+ enabled: false,
89
+ installed: false,
90
+ system: manifest.system ?? false,
91
+ })
92
+ .onConflictDoNothing();
93
+
94
+ console.log(
95
+ `[Module:ModuleManager] Module "${manifest.id}" uploaded and extracted.`,
96
+ );
97
+ await eventBus.publish("system:module:upload", {
98
+ moduleId: manifest.id,
99
+ manifest,
100
+ });
101
+ revalidatePath("/admin/modules");
102
+ revalidatePath("/module/module-manager");
103
+ return { success: true };
104
+ } catch (error) {
105
+ console.error(`[Module:ModuleManager] Upload error:`, error);
106
+ return { success: false, error: (error as Error).message };
107
+ }
108
+ }
109
+
110
+ export async function checkDiskWriteAccess() {
111
+ const modulesDir = await getModulesDir();
112
+ try {
113
+ await fs.access(modulesDir, constants.W_OK);
114
+ return { canWrite: true };
115
+ } catch (error) {
116
+ console.error(
117
+ "[Module:ModuleManager] No write access to modules directory:",
118
+ error,
119
+ );
120
+ return { canWrite: false };
121
+ }
122
+ }
123
+
124
+ export async function getModuleStatusAction(moduleId: string) {
125
+ unstable_noStore();
126
+ return await getModuleStatus(moduleId);
127
+ }
128
+
129
+ export async function getModulesAction() {
130
+ unstable_noStore();
131
+ return await getModules();
132
+ }
133
+
134
+ export async function getModuleDocumentation(moduleId: string) {
135
+ const modulesDir = await getModulesDir();
136
+ const docsDir = path.join(modulesDir, moduleId, "docs");
137
+ try {
138
+ const exists = await fs
139
+ .access(docsDir)
140
+ .then(() => true)
141
+ .catch(() => false);
142
+ if (!exists) return null;
143
+
144
+ const files = await fs.readdir(docsDir);
145
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
146
+
147
+ if (mdFiles.length === 0) return null;
148
+
149
+ const docs = await Promise.all(
150
+ mdFiles.map(async (file) => {
151
+ const content = await fs.readFile(path.join(docsDir, file), "utf-8");
152
+ return {
153
+ filename: file,
154
+ title: file
155
+ .replace(".md", "")
156
+ .split("-")
157
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
158
+ .join(" "),
159
+ content,
160
+ };
161
+ }),
162
+ );
163
+
164
+ return docs;
165
+ } catch (_error) {
166
+ return null;
167
+ }
168
+ }
@@ -0,0 +1,173 @@
1
+ "use server";
2
+
3
+ import type { SessionFlags } from "@arch-cadre/core";
4
+ import {
5
+ checkEmailAvailability,
6
+ createEmailVerificationRequest,
7
+ createSession,
8
+ eventBus,
9
+ filesystemService,
10
+ generateSessionToken,
11
+ getCurrentSession,
12
+ getUserPasswordHash,
13
+ invalidateUserSessions,
14
+ sendVerificationEmail,
15
+ setEmailVerificationRequestCookie,
16
+ setSessionTokenCookie,
17
+ updateUserAwatar,
18
+ updateUserName,
19
+ updateUserPassword,
20
+ verifyEmailInput,
21
+ verifyPasswordHash,
22
+ verifyPasswordStrength,
23
+ } from "@arch-cadre/core/server";
24
+ import { getTranslation } from "@arch-cadre/intl/server";
25
+ import { revalidatePath } from "next/cache";
26
+ import { redirect } from "next/navigation";
27
+ import type { ActionResult } from "../types";
28
+
29
+ export async function updatePasswordAction(
30
+ _prev: ActionResult,
31
+ formData: FormData,
32
+ ): Promise<ActionResult> {
33
+ const { t } = await getTranslation();
34
+ const { session, user } = await getCurrentSession();
35
+ if (session === null || user === null) {
36
+ return { success: false, message: t("Not authenticated") };
37
+ }
38
+
39
+ if (user.registered2FA && !session.two_factor_verified) {
40
+ return { success: false, message: t("Forbidden") };
41
+ }
42
+
43
+ const password = formData.get("password");
44
+ const newPassword = formData.get("new_password");
45
+
46
+ if (typeof password !== "string" || typeof newPassword !== "string") {
47
+ return { success: false, message: t("Invalid or missing fields") };
48
+ }
49
+
50
+ if (!verifyPasswordStrength(newPassword)) {
51
+ return { success: false, message: t("Weak password") };
52
+ }
53
+
54
+ const passwordHash = await getUserPasswordHash(user.id);
55
+ if (!passwordHash) return { success: false, message: t("Internal error") };
56
+
57
+ const validPassword = await verifyPasswordHash(passwordHash, password);
58
+ if (!validPassword) {
59
+ return { success: false, message: t("Incorrect password") };
60
+ }
61
+
62
+ await invalidateUserSessions(user.id);
63
+ await updateUserPassword(user.id, newPassword);
64
+
65
+ const sessionToken = await generateSessionToken();
66
+ const sessionFlags: SessionFlags = {
67
+ twoFactorVerified: session.two_factor_verified ?? false,
68
+ };
69
+ const newSession = await createSession(sessionToken, user.id, sessionFlags);
70
+ await setSessionTokenCookie(sessionToken, newSession.expiresAt);
71
+
72
+ await eventBus.publish(
73
+ "activity.create",
74
+ {
75
+ action: "profile.updated.password",
76
+ description: `User ${user?.name} updated their password`,
77
+ userId: user?.id,
78
+ metadata: {},
79
+ },
80
+ "profile",
81
+ );
82
+
83
+ return { success: true, message: t("Updated password") };
84
+ }
85
+
86
+ export async function updateEmailAction(
87
+ _prev: ActionResult,
88
+ formData: FormData,
89
+ ): Promise<ActionResult> {
90
+ const { t } = await getTranslation();
91
+ const { session, user } = await getCurrentSession();
92
+ if (session === null || user === null)
93
+ return { success: false, message: t("Not authenticated") };
94
+ if (user.registered2FA && !session.two_factor_verified)
95
+ return { success: false, message: t("Forbidden") };
96
+
97
+ const email = formData.get("email");
98
+ if (typeof email !== "string" || email === "") {
99
+ return { success: false, message: t("Please enter your email") };
100
+ }
101
+ if (!verifyEmailInput(email)) {
102
+ return { success: false, message: t("Please enter a valid email") };
103
+ }
104
+ const emailAvailable = await checkEmailAvailability(email);
105
+ if (!emailAvailable) {
106
+ return { success: false, message: t("This email is already used") };
107
+ }
108
+
109
+ const verificationRequest = await createEmailVerificationRequest(
110
+ user.id,
111
+ email,
112
+ );
113
+ await sendVerificationEmail(
114
+ verificationRequest.email,
115
+ verificationRequest.code,
116
+ );
117
+ await setEmailVerificationRequestCookie(verificationRequest);
118
+
119
+ await eventBus.publish(
120
+ "activity.create",
121
+ {
122
+ action: "profile.updated.email",
123
+ description: `User ${user?.name} updated their email`,
124
+ userId: user?.id,
125
+ metadata: { email },
126
+ },
127
+ "profile",
128
+ );
129
+
130
+ return redirect("/verify-email");
131
+ }
132
+
133
+ export async function updateProfileAction(
134
+ name: string,
135
+ image?: File,
136
+ ): Promise<{ error?: string; success?: boolean; message?: string }> {
137
+ const { session, user } = await getCurrentSession();
138
+
139
+ const { t } = await getTranslation();
140
+
141
+ if (session === null || user === null)
142
+ return { error: t("Not authenticated") };
143
+
144
+ if (!name || name.trim().length === 0)
145
+ return { error: t("Name is required") };
146
+ if (name.trim().length > 100) return { error: t("Name is too long") };
147
+
148
+ if (image instanceof File) {
149
+ const uploaded = await filesystemService.upload(image, "vercel-blob");
150
+
151
+ if ("error" in uploaded) {
152
+ return { error: uploaded.error };
153
+ }
154
+
155
+ await updateUserAwatar(user.id, uploaded.url);
156
+ }
157
+
158
+ await updateUserName(user.id, name.trim());
159
+ revalidatePath("/profile"); // Updated path
160
+
161
+ await eventBus.publish(
162
+ "activity.create",
163
+ {
164
+ action: "profile.updated",
165
+ description: `User ${user?.name} updated their profile`,
166
+ userId: user?.id,
167
+ metadata: { name, imageUrl: image instanceof File ? image.name : null },
168
+ },
169
+ "profile",
170
+ );
171
+
172
+ return { success: true, message: t("Profile updated successfully") };
173
+ }