plugin-git-manager 1.2.2 → 1.2.4

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 (76) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/client/326.03f7202ae14b60ae.js +10 -0
  4. package/dist/client/434.afb45fa69887568e.js +10 -0
  5. package/dist/client/878.f99a81c2e6b4f602.js +10 -0
  6. package/dist/client/index.js +10 -1
  7. package/dist/client-v2/326.42b7b53651ffab3a.js +10 -0
  8. package/dist/client-v2/434.e65de282926677d6.js +10 -0
  9. package/dist/client-v2/878.06d6d1fe4fdb05f8.js +10 -0
  10. package/dist/client-v2/index.js +10 -0
  11. package/dist/externalVersion.js +16 -5
  12. package/dist/index.js +9 -0
  13. package/dist/locale/en-US.json +8 -1
  14. package/dist/locale/vi-VN.json +8 -1
  15. package/dist/server/actions/git-actions.js +43 -6
  16. package/dist/server/actions/gitlab-api.js +9 -0
  17. package/dist/server/actions/poller.js +9 -0
  18. package/dist/server/actions/review.js +9 -0
  19. package/dist/server/actions/role-permissions.js +145 -0
  20. package/dist/server/ai-tools.js +9 -0
  21. package/dist/server/collections/gitCodeReviews.js +9 -0
  22. package/dist/server/collections/gitRepositories.js +9 -0
  23. package/dist/server/collections/gitReviewFlows.js +9 -0
  24. package/dist/server/index.js +9 -0
  25. package/dist/server/migrations/20260508000000-add-auto-review-flow-id.js +9 -0
  26. package/dist/server/plugin.js +27 -1
  27. package/dist/server/poller.js +9 -0
  28. package/dist/server/utils/gitlab-url.js +9 -0
  29. package/dist/server/utils/redact.js +9 -0
  30. package/package.json +8 -4
  31. package/src/client/components/GitManagerSettings.tsx +1 -11
  32. package/src/client/components/RepositoryPermissions.tsx +130 -0
  33. package/src/client/index.tsx +36 -2
  34. package/src/client-v2/index.tsx +1 -0
  35. package/src/client-v2/plugin.tsx +32 -0
  36. package/src/locale/en-US.json +8 -1
  37. package/src/locale/vi-VN.json +8 -1
  38. package/src/server/__tests__/smoke.test.ts +17 -0
  39. package/src/server/actions/git-actions.ts +430 -430
  40. package/src/server/actions/role-permissions.ts +128 -0
  41. package/src/server/plugin.ts +46 -25
  42. package/dist/client/187.d5545b7cc8b90bfc.js +0 -1
  43. package/dist/client/228.7588a0707cb3694a.js +0 -1
  44. package/dist/client/ai-context.d.ts +0 -8
  45. package/dist/client/components/AIEmployeeSelect.d.ts +0 -13
  46. package/dist/client/components/CommitHistory.d.ts +0 -2
  47. package/dist/client/components/FileExplorer.d.ts +0 -2
  48. package/dist/client/components/GitManagerSettings.d.ts +0 -2
  49. package/dist/client/components/GitOperations.d.ts +0 -2
  50. package/dist/client/components/LLMModelSelect.d.ts +0 -10
  51. package/dist/client/components/LLMServiceSelect.d.ts +0 -9
  52. package/dist/client/components/MarkdownView.d.ts +0 -10
  53. package/dist/client/components/MergeRequests.d.ts +0 -2
  54. package/dist/client/components/PollingStatus.d.ts +0 -2
  55. package/dist/client/components/RepositoryConfig.d.ts +0 -2
  56. package/dist/client/components/ReviewFlows.d.ts +0 -2
  57. package/dist/client/components/ReviewHistory.d.ts +0 -4
  58. package/dist/client/components/RunReviewButton.d.ts +0 -31
  59. package/dist/client/context/GitManagerContext.d.ts +0 -26
  60. package/dist/client/index.d.ts +0 -5
  61. package/dist/client/locale.d.ts +0 -2
  62. package/dist/index.d.ts +0 -2
  63. package/dist/server/actions/git-actions.d.ts +0 -13
  64. package/dist/server/actions/gitlab-api.d.ts +0 -4
  65. package/dist/server/actions/poller.d.ts +0 -8
  66. package/dist/server/actions/review.d.ts +0 -40
  67. package/dist/server/ai-tools.d.ts +0 -10
  68. package/dist/server/collections/gitCodeReviews.d.ts +0 -2
  69. package/dist/server/collections/gitRepositories.d.ts +0 -2
  70. package/dist/server/collections/gitReviewFlows.d.ts +0 -2
  71. package/dist/server/index.d.ts +0 -2
  72. package/dist/server/migrations/20260508000000-add-auto-review-flow-id.d.ts +0 -6
  73. package/dist/server/plugin.d.ts +0 -12
  74. package/dist/server/poller.d.ts +0 -30
  75. package/dist/server/utils/gitlab-url.d.ts +0 -21
  76. package/dist/server/utils/redact.d.ts +0 -13
@@ -0,0 +1,145 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var role_permissions_exports = {};
28
+ __export(role_permissions_exports, {
29
+ rolePermissions: () => rolePermissions,
30
+ updateRolePermissions: () => updateRolePermissions
31
+ });
32
+ module.exports = __toCommonJS(role_permissions_exports);
33
+ function getRecordValue(record, key) {
34
+ return (record == null ? void 0 : record.get) ? record.get(key) : record == null ? void 0 : record[key];
35
+ }
36
+ async function rolePermissions(ctx, _next) {
37
+ var _a;
38
+ const currentRoles = Array.isArray((_a = ctx.state) == null ? void 0 : _a.currentRoles) ? ctx.state.currentRoles : [];
39
+ if (!currentRoles.includes("root")) {
40
+ const canAccessRoles = await ctx.app.acl.can({ roles: currentRoles, resource: "roles", action: "update" });
41
+ if (!canAccessRoles) return ctx.throw(403, "Permission denied");
42
+ }
43
+ const roleName = ctx.action.params.roleName;
44
+ if (!roleName) return ctx.throw(400, "roleName is required");
45
+ const resource = await ctx.db.getRepository("rolesResources").findOne({
46
+ filter: { roleName, name: "gitRepositories" }
47
+ });
48
+ if (!resource) {
49
+ ctx.body = { data: { read: [], write: [] } };
50
+ return;
51
+ }
52
+ const actions = await ctx.db.getRepository("rolesResourcesActions").find({
53
+ filter: { rolesResourceId: getRecordValue(resource, "id") },
54
+ appends: ["scope"]
55
+ });
56
+ const result = { read: [], write: [] };
57
+ for (const action of actions) {
58
+ const actionData = action.toJSON ? action.toJSON() : action;
59
+ const actionName = actionData.name;
60
+ if (result[actionName]) {
61
+ const scopeRow = actionData.scope;
62
+ const scope = (scopeRow == null ? void 0 : scopeRow.scope) || scopeRow;
63
+ try {
64
+ if (scope && scope.$and && scope.$and[0] && scope.$and[0].id) {
65
+ if (scope.$and[0].id.$in) {
66
+ result[actionName] = scope.$and[0].id.$in;
67
+ } else if (typeof scope.$and[0].id === "number" || typeof scope.$and[0].id === "string") {
68
+ result[actionName] = [scope.$and[0].id];
69
+ }
70
+ }
71
+ } catch (e) {
72
+ }
73
+ }
74
+ }
75
+ ctx.body = result;
76
+ }
77
+ async function updateRolePermissions(ctx, _next) {
78
+ var _a, _b, _c;
79
+ const currentRoles = Array.isArray((_a = ctx.state) == null ? void 0 : _a.currentRoles) ? ctx.state.currentRoles : [];
80
+ if (!currentRoles.includes("root")) {
81
+ const canAccessRoles = await ctx.app.acl.can({ roles: currentRoles, resource: "roles", action: "update" });
82
+ if (!canAccessRoles) return ctx.throw(403, "Permission denied");
83
+ }
84
+ const payload = ctx.action.params.values || ctx.request.body || {};
85
+ const roleName = payload.roleName || ctx.action.params.roleName;
86
+ const permissions = payload.values || {};
87
+ if (!roleName) return ctx.throw(400, "roleName is required");
88
+ const resourceRepo = ctx.db.getRepository("rolesResources");
89
+ let resource = await resourceRepo.findOne({ filter: { roleName, name: "gitRepositories" } });
90
+ if (!resource) {
91
+ resource = await resourceRepo.create({
92
+ values: { roleName, name: "gitRepositories", usingActionsConfig: true }
93
+ });
94
+ } else if (!getRecordValue(resource, "usingActionsConfig")) {
95
+ await resource.update({ usingActionsConfig: true });
96
+ }
97
+ const rolesResourceId = getRecordValue(resource, "id");
98
+ const actionsRepo = ctx.db.getRepository("rolesResourcesActions");
99
+ for (const actionName of ["read", "write"]) {
100
+ const ids = permissions[actionName] || [];
101
+ const action = await actionsRepo.findOne({
102
+ filter: { rolesResourceId, name: actionName },
103
+ appends: ["scope"]
104
+ });
105
+ if (ids.length === 0) {
106
+ if (action) await action.destroy();
107
+ } else {
108
+ const scopeJson = { $and: [{ id: { $in: ids } }] };
109
+ if (action) {
110
+ const actionData = action.toJSON ? action.toJSON() : action;
111
+ const actionScope = actionData.scope;
112
+ const actionScopeId = getRecordValue(actionScope, "id") || actionData.scopeId || getRecordValue(action, "scopeId");
113
+ if (actionScopeId) {
114
+ await ctx.db.getRepository("rolesResourcesScopes").update({
115
+ filterByTk: actionScopeId,
116
+ values: { scope: scopeJson }
117
+ });
118
+ } else {
119
+ const newScope = await ctx.db.getRepository("rolesResourcesScopes").create({
120
+ values: { scope: scopeJson }
121
+ });
122
+ await action.update({ scopeId: getRecordValue(newScope, "id") });
123
+ }
124
+ } else {
125
+ const newScope = await ctx.db.getRepository("rolesResourcesScopes").create({
126
+ values: { scope: scopeJson }
127
+ });
128
+ await actionsRepo.create({
129
+ values: { rolesResourceId, name: actionName, scopeId: getRecordValue(newScope, "id") }
130
+ });
131
+ }
132
+ }
133
+ }
134
+ try {
135
+ await resource.writeToACL({ acl: ctx.app.acl });
136
+ } catch (e) {
137
+ (_c = (_b = ctx.logger) == null ? void 0 : _b.warn) == null ? void 0 : _c.call(_b, "[git-manager] Failed to write resource to ACL memory cache:", e);
138
+ }
139
+ ctx.body = { data: "ok" };
140
+ }
141
+ // Annotate the CommonJS export names for ESM import in node:
142
+ 0 && (module.exports = {
143
+ rolePermissions,
144
+ updateRolePermissions
145
+ });
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __create = Object.create;
2
11
  var __defProp = Object.defineProperty;
3
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -39,6 +48,7 @@ var gitActions = __toESM(require("./actions/git-actions"));
39
48
  var gitlabApi = __toESM(require("./actions/gitlab-api"));
40
49
  var reviewActions = __toESM(require("./actions/review"));
41
50
  var pollerActions = __toESM(require("./actions/poller"));
51
+ var rolePermissionsActions = __toESM(require("./actions/role-permissions"));
42
52
  var import_review = require("./actions/review");
43
53
  var import_ai_tools = require("./ai-tools");
44
54
  var import_poller = require("./poller");
@@ -81,7 +91,9 @@ class PluginGitManagerServer extends import_server.Plugin {
81
91
  reviewApprovePost: reviewActions.reviewApprovePost,
82
92
  reviewReject: reviewActions.reviewReject,
83
93
  pollNow: pollerActions.pollNow,
84
- pollerStatus: pollerActions.pollerStatus
94
+ pollerStatus: pollerActions.pollerStatus,
95
+ rolePermissions: rolePermissionsActions.rolePermissions,
96
+ updateRolePermissions: rolePermissionsActions.updateRolePermissions
85
97
  }
86
98
  });
87
99
  this.app.use(async (ctx, next) => {
@@ -166,6 +178,20 @@ class PluginGitManagerServer extends import_server.Plugin {
166
178
  "gitManager:pollNow"
167
179
  ]
168
180
  });
181
+ this.app.acl.registerSnippet({
182
+ name: `pm.${this.name}.repositories`,
183
+ actions: [
184
+ "gitRepositories:*"
185
+ ]
186
+ });
187
+ this.app.acl.registerSnippet({
188
+ name: `pm.${this.name}.manage`,
189
+ actions: [
190
+ `pm.${this.name}.repositories`,
191
+ `pm.${this.name}.read`,
192
+ `pm.${this.name}.write`
193
+ ]
194
+ });
169
195
  this.app.resourceManager.use(async (ctx, next) => {
170
196
  var _a, _b, _c, _d, _e;
171
197
  if (((_a = ctx.action) == null ? void 0 : _a.resourceName) === "gitRepositories" && ["create", "update"].includes((_b = ctx.action) == null ? void 0 : _b.actionName)) {
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
1
10
  var __defProp = Object.defineProperty;
2
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Git Manager",
4
4
  "displayName.zh-CN": "Git 管理器",
5
5
  "description": "Manage Git repositories with PAT authentication - pull, push, fetch, diff, file browsing",
6
- "version": "1.2.2",
6
+ "version": "1.2.4",
7
7
  "license": "Apache-2.0",
8
8
  "main": "dist/server/index.js",
9
9
  "files": [
@@ -12,7 +12,9 @@
12
12
  "client.js",
13
13
  "server.js",
14
14
  "client.d.ts",
15
- "server.d.ts"
15
+ "server.d.ts",
16
+ "client-v2.js",
17
+ "client-v2.d.ts"
16
18
  ],
17
19
  "nocobase": {
18
20
  "supportedVersions": [
@@ -24,7 +26,9 @@
24
26
  "@nocobase/client": "2.x",
25
27
  "@nocobase/server": "2.x",
26
28
  "@nocobase/database": "2.x",
27
- "@nocobase/test": "2.x"
29
+ "@nocobase/test": "2.x",
30
+ "@nocobase/client-v2": "2.x",
31
+ "@nocobase/flow-engine": "2.x"
28
32
  },
29
33
  "optionalPeerDependencies": {
30
34
  "@nocobase/ai": "2.x",
@@ -34,4 +38,4 @@
34
38
  "dependencies": {
35
39
  "simple-git": "^3.27.0"
36
40
  }
37
- }
41
+ }
@@ -5,7 +5,6 @@ import {
5
5
  RobotOutlined, AuditOutlined, ClockCircleOutlined,
6
6
  } from '@ant-design/icons';
7
7
  import { GitManagerProvider, useGitManager } from '../context/GitManagerContext';
8
- import { RepositoryConfig } from './RepositoryConfig';
9
8
  import { FileExplorer } from './FileExplorer';
10
9
  import { CommitHistory } from './CommitHistory';
11
10
  import { MergeRequests } from './MergeRequests';
@@ -76,18 +75,9 @@ const GitManagerContent: React.FC = () => {
76
75
 
77
76
  <Tabs
78
77
  type="card"
79
- defaultActiveKey="repos"
78
+ defaultActiveKey="explorer"
80
79
  destroyInactiveTabPane
81
80
  items={[
82
- {
83
- key: 'repos',
84
- label: (
85
- <span>
86
- <DatabaseOutlined /> {t('Repositories')}
87
- </span>
88
- ),
89
- children: <RepositoryConfig />,
90
- },
91
81
  {
92
82
  key: 'explorer',
93
83
  label: (
@@ -0,0 +1,130 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useAPIClient } from '@nocobase/client';
3
+ import { Table, Checkbox, Spin, Typography, message } from 'antd';
4
+
5
+ export const RepositoryPermissions = ({ activeRole }) => {
6
+ const api = useAPIClient();
7
+ const [loading, setLoading] = useState(false);
8
+ const [repositories, setRepositories] = useState([]);
9
+ const [permissions, setPermissions] = useState({ read: [], write: [] });
10
+
11
+ useEffect(() => {
12
+ if (activeRole?.name) {
13
+ loadData();
14
+ }
15
+ }, [activeRole?.name]);
16
+
17
+ const loadData = async () => {
18
+ setLoading(true);
19
+ try {
20
+ const [repoRes, permRes] = await Promise.all([
21
+ api.request({
22
+ resource: 'gitRepositories',
23
+ action: 'list',
24
+ params: { paginate: false, sort: ['name'] },
25
+ }),
26
+ api.request({
27
+ url: 'gitManager:rolePermissions',
28
+ method: 'get',
29
+ params: { roleName: activeRole.name },
30
+ }),
31
+ ]);
32
+ const fetchedPerms = permRes?.data?.data || permRes?.data || { read: [], write: [] };
33
+ setRepositories(repoRes?.data?.data || []);
34
+ setPermissions({
35
+ read: fetchedPerms.read || [],
36
+ write: fetchedPerms.write || [],
37
+ });
38
+ } catch (e) {
39
+ console.error(e);
40
+ message.error('Failed to load repository permissions');
41
+ }
42
+ setLoading(false);
43
+ };
44
+
45
+ const handleToggle = async (repositoryId: number, actionName: string, checked: boolean) => {
46
+ const currentList = permissions[actionName] || [];
47
+ const newList = checked
48
+ ? [...new Set([...currentList, repositoryId])]
49
+ : currentList.filter((id: number) => id !== repositoryId);
50
+
51
+ const newPermissions = { ...permissions, [actionName]: newList };
52
+ const prevPermissions = permissions;
53
+ setPermissions(newPermissions);
54
+
55
+ try {
56
+ await api.request({
57
+ url: 'gitManager:updateRolePermissions',
58
+ method: 'post',
59
+ data: {
60
+ roleName: activeRole.name,
61
+ values: newPermissions,
62
+ },
63
+ });
64
+ } catch {
65
+ message.error('Failed to update permissions');
66
+ setPermissions(prevPermissions);
67
+ }
68
+ };
69
+
70
+ const columns = [
71
+ {
72
+ title: 'Repository Name',
73
+ dataIndex: 'name',
74
+ key: 'name',
75
+ render: (text: string) => <Typography.Text strong>{text}</Typography.Text>,
76
+ },
77
+ {
78
+ title: 'Repository URL',
79
+ dataIndex: 'repoUrl',
80
+ key: 'repoUrl',
81
+ ellipsis: true,
82
+ render: (text: string) => <Typography.Text type="secondary">{text}</Typography.Text>,
83
+ },
84
+ {
85
+ title: 'Read (List, Browse Files, View History)',
86
+ key: 'read',
87
+ width: 100,
88
+ render: (_: any, record: any) => (
89
+ <Checkbox
90
+ checked={(permissions.read || []).includes(record.id)}
91
+ onChange={(e) => handleToggle(record.id, 'read', e.target.checked)}
92
+ />
93
+ ),
94
+ },
95
+ {
96
+ title: 'Write (Clone, Pull, Push, Review)',
97
+ key: 'write',
98
+ width: 100,
99
+ render: (_: any, record: any) => (
100
+ <Checkbox
101
+ checked={(permissions.write || []).includes(record.id)}
102
+ onChange={(e) => handleToggle(record.id, 'write', e.target.checked)}
103
+ />
104
+ ),
105
+ },
106
+ ];
107
+
108
+ if (loading && !repositories.length) {
109
+ return <Spin style={{ display: 'block', margin: '40px auto' }} />;
110
+ }
111
+
112
+ return (
113
+ <div style={{ padding: 16 }}>
114
+ <div style={{ marginBottom: 16 }}>
115
+ <Typography.Text type="secondary">
116
+ Configure access permissions for each repository for the role{' '}
117
+ <Typography.Text strong>{activeRole?.title || activeRole?.name}</Typography.Text>.
118
+ </Typography.Text>
119
+ </div>
120
+ <Table
121
+ dataSource={repositories}
122
+ columns={columns}
123
+ rowKey="id"
124
+ pagination={false}
125
+ size="small"
126
+ bordered
127
+ />
128
+ </div>
129
+ );
130
+ };
@@ -10,13 +10,33 @@ const GitManagerSettings = React.lazy(() =>
10
10
  import('./components/GitManagerSettings').then((m) => ({ default: m.GitManagerSettings })),
11
11
  );
12
12
 
13
+ const RepositoryConfig = React.lazy(() =>
14
+ import('./components/RepositoryConfig').then((m) => ({ default: m.RepositoryConfig })),
15
+ );
16
+
17
+ import PluginACLClient from '@nocobase/plugin-acl/client';
18
+ import { RepositoryPermissions } from './components/RepositoryPermissions';
19
+
13
20
  export class PluginGitManagerClient extends Plugin {
14
21
  async load() {
15
- (this as any).app.pluginSettingsManager.add('git-manager', {
22
+ // Parent group with no direct component — acts as a category in settings
23
+ this.app.pluginSettingsManager.add('git-manager', {
16
24
  title: (this as any).t('Git Manager'),
17
25
  icon: 'BranchesOutlined',
26
+ });
27
+
28
+ // Repositories config — separate group for ACL-granular access
29
+ this.app.pluginSettingsManager.add('git-manager.repositories', {
30
+ title: (this as any).t('Repositories'),
31
+ Component: RepositoryConfig,
32
+ aclSnippet: `pm.plugin-git-manager.repositories`,
33
+ });
34
+
35
+ // Full management — requires full plugin permission
36
+ this.app.pluginSettingsManager.add('git-manager.manage', {
37
+ title: (this as any).t('Manage'),
18
38
  Component: GitManagerSettings,
19
- aclSnippet: 'pm.plugin-git-manager',
39
+ aclSnippet: `pm.plugin-git-manager.manage`,
20
40
  });
21
41
 
22
42
  const aiManager = ((this as any).app as any).aiManager;
@@ -25,6 +45,20 @@ export class PluginGitManagerClient extends Plugin {
25
45
  aiManager.registerWorkContext('git-merge-request', GitMergeRequestWorkContext);
26
46
  aiManager.registerWorkContext('git-commit', GitCommitWorkContext);
27
47
  }
48
+
49
+ const aclPlugin = this.app.pm.get(PluginACLClient);
50
+ if (aclPlugin) {
51
+ aclPlugin.settingsUI.addPermissionsTab(({ t, TabLayout, activeRole }) => ({
52
+ key: 'git-manager',
53
+ label: 'Git Manager',
54
+ sort: 25,
55
+ children: (
56
+ <TabLayout>
57
+ <RepositoryPermissions activeRole={activeRole} />
58
+ </TabLayout>
59
+ ),
60
+ }));
61
+ }
28
62
  }
29
63
  }
30
64
 
@@ -0,0 +1 @@
1
+ export { default } from './plugin';
@@ -0,0 +1,32 @@
1
+ import { Plugin, Application } from '@nocobase/client-v2';
2
+ import React from 'react';
3
+
4
+ export class PluginGitManagerClient extends Plugin<Record<string, never>, Application> {
5
+ async load() {
6
+ this.pluginSettingsManager.addMenuItem({
7
+ key: 'git-manager',
8
+ title: this.t('Git Manager'),
9
+ icon: 'BranchesOutlined',
10
+
11
+ });
12
+
13
+ this.pluginSettingsManager.addPageTabItem({
14
+ menuKey: 'git-manager',
15
+ key: 'repositories',
16
+ title: this.t('Repositories'),
17
+ aclSnippet: 'pm.plugin-git-manager.repositories',
18
+ componentLoader: () => import('../client/components/RepositoryConfig').then(m => ({ default: m.RepositoryConfig })),
19
+ });
20
+
21
+ this.pluginSettingsManager.addPageTabItem({
22
+ menuKey: 'git-manager',
23
+ key: 'manage',
24
+ title: this.t('Manage'),
25
+ aclSnippet: 'pm.plugin-git-manager.manage',
26
+ componentLoader: () => import('../client/components/GitManagerSettings').then(m => ({ default: m.GitManagerSettings })),
27
+ });
28
+
29
+ }
30
+ }
31
+
32
+ export default PluginGitManagerClient;
@@ -186,5 +186,12 @@
186
186
  "Only flows with automatic trigger modes are shown": "Only flows with automatic trigger modes are shown",
187
187
  "Fallback": "Fallback",
188
188
  "AI employee not found": "AI employee not found",
189
- "Failed to open AI chat": "Failed to open AI chat"
189
+ "Failed to open AI chat": "Failed to open AI chat",
190
+ "Manage": "Manage",
191
+ "Repository Permissions": "Repository Permissions",
192
+ "Read (List, Browse Files, View History)": "Read (List, Browse Files, View History)",
193
+ "Write (Clone, Pull, Push, Review)": "Write (Clone, Pull, Push, Review)",
194
+ "Configure access permissions for each repository for the role {{roleName}}": "Configure access permissions for each repository for the role {{roleName}}",
195
+ "Failed to load repository permissions": "Failed to load repository permissions",
196
+ "Failed to update permissions": "Failed to update permissions"
190
197
  }
@@ -179,5 +179,12 @@
179
179
  "new": "mới",
180
180
  "Reviewed SHA": "SHA đã đánh giá",
181
181
  "Latest SHA": "SHA mới nhất",
182
- "All triggers": "Tất cả nguồn"
182
+ "All triggers": "Tất cả nguồn",
183
+ "Manage": "Quản lý",
184
+ "Repository Permissions": "Phân quyền kho mã nguồn",
185
+ "Read (List, Browse Files, View History)": "Đọc (Xem danh sách, Duyệt tệp, Xem lịch sử)",
186
+ "Write (Clone, Pull, Push, Review)": "Ghi (Sao chép, Kéo về, Đẩy lên, Đánh giá)",
187
+ "Configure access permissions for each repository for the role {{roleName}}": "Cấu hình quyền truy cập cho từng kho mã nguồn đối với vai trò {{roleName}}",
188
+ "Failed to load repository permissions": "Không thể tải phân quyền kho mã nguồn",
189
+ "Failed to update permissions": "Cập nhật phân quyền thất bại"
183
190
  }
@@ -0,0 +1,17 @@
1
+ import { createMockServer } from '@nocobase/test';
2
+
3
+ describe('Git Manager plugin smoke', () => {
4
+ let app;
5
+
6
+ afterEach(async () => {
7
+ await app?.destroy();
8
+ });
9
+
10
+ it('loads without starting the full app', async () => {
11
+ app = await createMockServer({
12
+ plugins: ["nocobase","git-manager"],
13
+ });
14
+
15
+ expect(app).toBeTruthy();
16
+ });
17
+ });