Skip to content

Commit 46da495

Browse files
author
Lasim
committed
feat(teams): add member management functionality and UI updates
- Introduced member-related components for team management: AddMemberModal, RemoveMemberModal, MemberRow, and MembersList. - Added TeamMember and DisplayMember types for better type safety. - Implemented Sonner component for toast notifications. - Updated English locale files with new translations for team and environment variable management. - Enhanced MCP installation view to include user role checks and installation update handling. - Refactored team management view to utilize tabs for better organization of team information, members, and danger zone actions. - Improved team selection logic to handle selected team context and fallback to default team. - Updated gateway package dependencies for better compatibility.
1 parent f3fd98e commit 46da495

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+6242
-1418
lines changed

package-lock.json

Lines changed: 447 additions & 300 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/backend/api-spec.json

Lines changed: 1225 additions & 94 deletions
Large diffs are not rendered by default.

services/backend/api-spec.yaml

Lines changed: 812 additions & 86 deletions
Large diffs are not rendered by default.

services/backend/drizzle/migrations_sqlite/0000_brave_maddog.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,6 @@ INSERT INTO `roles` (`id`, `name`, `description`, `permissions`, `is_system_role
212212
('global_user', 'Global User', 'Standard user with basic profile access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
213213
('team_admin', 'Team Administrator', 'Team management with member and credential access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
214214
('team_user', 'Team User', 'Basic team member with view access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000);
215+
216+
INSERT INTO `mcpCategories` (`id`, `name`, `description`, `icon`, `sort_order`, `created_at`)
217+
VALUES ('default-category-id', 'Default', 'Default category', 'Tags', 0, strftime('%s', 'now') * 1000);

services/backend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,19 @@
5353
"@types/pug": "^2.0.10",
5454
"@types/supertest": "^6.0.3",
5555
"@typescript-eslint/eslint-plugin": "^8.36.0",
56-
"@typescript-eslint/parser": "^8.35.1",
56+
"@typescript-eslint/parser": "^8.38.0",
5757
"@vitest/coverage-v8": "^3.2.3",
5858
"drizzle-kit": "^0.31.4",
5959
"eslint": "^9.31.0",
6060
"fs-extra": "^11.3.0",
6161
"jest": "^30.0.4",
6262
"nodemon": "^3.1.10",
63-
"release-it": "^19.0.2",
63+
"release-it": "^19.0.4",
6464
"supertest": "^7.1.3",
6565
"ts-jest": "^29.4.0",
6666
"ts-node": "^10.9.2",
6767
"typescript": "^5.8.3",
68-
"typescript-eslint": "^8.37.0",
68+
"typescript-eslint": "^8.38.0",
6969
"vitest": "^3.2.3"
7070
},
7171
"overrides": {

services/backend/src/permissions/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ export const ROLE_DEFINITIONS = {
7676
'cloud_credentials.view',
7777
'mcp.servers.read',
7878
'mcp.installations.view',
79-
'mcp.installations.create',
80-
'mcp.installations.edit',
81-
'mcp.installations.delete',
8279
],
8380
} as const;
8481

services/backend/src/routes/mcp/installations/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { type FastifyInstance } from 'fastify';
22
import createInstallationRoute from './create';
33
import listInstallationsRoute from './list';
44
import getInstallationRoute from './get';
5+
import updateInstallationRoute from './update';
6+
import updateEnvironmentVariablesRoute from './updateEnvironmentVars';
57
import getClientConfigRoute from './config';
68
import deleteInstallationRoute from './delete';
79

@@ -10,6 +12,8 @@ export default async function installationsRoutes(fastify: FastifyInstance) {
1012
await fastify.register(createInstallationRoute);
1113
await fastify.register(listInstallationsRoute);
1214
await fastify.register(getInstallationRoute);
15+
await fastify.register(updateInstallationRoute);
16+
await fastify.register(updateEnvironmentVariablesRoute);
1317
await fastify.register(getClientConfigRoute);
1418
await fastify.register(deleteInstallationRoute);
1519
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { type FastifyInstance } from 'fastify';
2+
import { z } from 'zod';
3+
import { createSchema } from 'zod-openapi';
4+
import { requireTeamPermission } from '../../../middleware/roleMiddleware';
5+
import { McpInstallationService } from '../../../services/mcpInstallationService';
6+
import { getDb } from '../../../db';
7+
8+
// Request schema
9+
type UpdateInstallationSchema = {
10+
installation_name?: string;
11+
user_environment_variables?: Record<string, string>;
12+
};
13+
14+
// Response schemas
15+
const installationSchema = z.object({
16+
id: z.string(),
17+
team_id: z.string(),
18+
server_id: z.string(),
19+
user_id: z.string(),
20+
installation_name: z.string(),
21+
installation_type: z.enum(['local', 'cloud']),
22+
user_environment_variables: z.record(z.string(), z.string()).optional(),
23+
created_at: z.string(),
24+
updated_at: z.string(),
25+
last_used_at: z.string().nullable(),
26+
server: z.object({
27+
id: z.string(),
28+
name: z.string(),
29+
description: z.string(),
30+
github_url: z.string().nullable(),
31+
homepage_url: z.string().nullable(),
32+
author_name: z.string().nullable(),
33+
language: z.string(),
34+
runtime: z.string(),
35+
status: z.enum(['active', 'deprecated', 'maintenance']),
36+
tags: z.array(z.string()).nullable(),
37+
environment_variables: z.array(z.any()).nullable(),
38+
category_id: z.string().nullable()
39+
}).optional()
40+
});
41+
42+
const successResponseSchema = z.object({
43+
success: z.boolean().default(true),
44+
data: installationSchema,
45+
message: z.string()
46+
});
47+
48+
const errorResponseSchema = z.object({
49+
success: z.boolean().default(false),
50+
error: z.string()
51+
});
52+
53+
export default async function updateInstallationRoute(fastify: FastifyInstance) {
54+
fastify.put<{
55+
Params: { teamId: string; installationId: string };
56+
Body: UpdateInstallationSchema;
57+
}>('/teams/:teamId/mcp/installations/:installationId', {
58+
schema: {
59+
tags: ['MCP Installations'],
60+
summary: 'Update MCP installation',
61+
description: 'Updates an existing MCP server installation. Can update installation name and environment variables. Requires Content-Type: application/json header when sending request body.',
62+
security: [{ cookieAuth: [] }],
63+
params: {
64+
type: 'object',
65+
properties: {
66+
teamId: { type: 'string', minLength: 1 },
67+
installationId: { type: 'string', minLength: 1 }
68+
},
69+
required: ['teamId', 'installationId'],
70+
additionalProperties: false
71+
},
72+
body: {
73+
type: 'object',
74+
properties: {
75+
installation_name: { type: 'string', minLength: 1 },
76+
user_environment_variables: {
77+
type: 'object',
78+
additionalProperties: { type: 'string' }
79+
}
80+
},
81+
additionalProperties: false
82+
},
83+
response: {
84+
200: createSchema(successResponseSchema.describe('Installation updated successfully')),
85+
400: createSchema(errorResponseSchema.describe('Bad Request - Validation error or installation name conflict')),
86+
401: createSchema(errorResponseSchema.describe('Unauthorized - Authentication required')),
87+
403: createSchema(errorResponseSchema.describe('Forbidden - Insufficient permissions')),
88+
404: createSchema(errorResponseSchema.describe('Not Found - Installation not found')),
89+
500: createSchema(errorResponseSchema.describe('Internal Server Error'))
90+
}
91+
},
92+
preValidation: requireTeamPermission('mcp.installations.edit')
93+
}, async (request, reply) => {
94+
const { teamId, installationId } = request.params;
95+
const userId = request.user!.id;
96+
const updateData = request.body;
97+
98+
request.log.info({
99+
operation: 'update_mcp_installation',
100+
teamId,
101+
installationId,
102+
userId,
103+
updateFields: Object.keys(updateData)
104+
}, 'Updating MCP installation');
105+
106+
try {
107+
const db = getDb();
108+
const installationService = new McpInstallationService(db, request.log);
109+
110+
const updatedInstallation = await installationService.updateInstallation(
111+
installationId,
112+
teamId,
113+
userId,
114+
updateData
115+
);
116+
117+
if (!updatedInstallation) {
118+
request.log.warn({
119+
operation: 'update_mcp_installation',
120+
teamId,
121+
installationId,
122+
userId
123+
}, 'MCP installation not found for update');
124+
125+
const errorResponse = {
126+
success: false,
127+
error: 'Installation not found'
128+
};
129+
const jsonString = JSON.stringify(errorResponse);
130+
return reply.status(404).type('application/json').send(jsonString);
131+
}
132+
133+
request.log.info({
134+
operation: 'update_mcp_installation',
135+
teamId,
136+
installationId,
137+
userId
138+
}, 'Successfully updated MCP installation');
139+
140+
const successResponse = {
141+
success: true,
142+
data: {
143+
...updatedInstallation,
144+
created_at: updatedInstallation.created_at.toISOString(),
145+
updated_at: updatedInstallation.updated_at.toISOString(),
146+
last_used_at: updatedInstallation.last_used_at?.toISOString() || null
147+
},
148+
message: 'Installation updated successfully'
149+
};
150+
const jsonString = JSON.stringify(successResponse);
151+
return reply.status(200).type('application/json').send(jsonString);
152+
153+
} catch (error) {
154+
request.log.error({
155+
operation: 'update_mcp_installation',
156+
error,
157+
teamId,
158+
installationId,
159+
userId
160+
}, 'Failed to update MCP installation');
161+
162+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
163+
164+
const errorResponse = {
165+
success: false,
166+
error: errorMessage
167+
};
168+
const jsonString = JSON.stringify(errorResponse);
169+
return reply.status(500).type('application/json').send(jsonString);
170+
}
171+
});
172+
}

0 commit comments

Comments
 (0)