@abpjs/saas 0.7.2
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/LICENSE +165 -0
- package/README.md +374 -0
- package/dist/index.d.mts +470 -0
- package/dist/index.d.ts +470 -0
- package/dist/index.js +1187 -0
- package/dist/index.mjs +1172 -0
- package/package.json +90 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
EditionsComponent: () => EditionsComponent,
|
|
24
|
+
SAAS_ROUTES: () => SAAS_ROUTES,
|
|
25
|
+
SaasService: () => SaasService,
|
|
26
|
+
TenantsComponent: () => TenantsComponent,
|
|
27
|
+
useEditions: () => useEditions,
|
|
28
|
+
useTenants: () => useTenants
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/constants/routes.ts
|
|
33
|
+
var SAAS_ROUTES = {
|
|
34
|
+
routes: [
|
|
35
|
+
{
|
|
36
|
+
name: "Saas",
|
|
37
|
+
path: "saas",
|
|
38
|
+
layout: "application",
|
|
39
|
+
order: 50,
|
|
40
|
+
children: [
|
|
41
|
+
{
|
|
42
|
+
name: "Tenants",
|
|
43
|
+
path: "tenants",
|
|
44
|
+
order: 1,
|
|
45
|
+
requiredPolicy: "Saas.Tenants"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "Editions",
|
|
49
|
+
path: "editions",
|
|
50
|
+
order: 2,
|
|
51
|
+
requiredPolicy: "Saas.Editions"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/services/saas.service.ts
|
|
59
|
+
var SaasService = class {
|
|
60
|
+
constructor(restService) {
|
|
61
|
+
this.restService = restService;
|
|
62
|
+
}
|
|
63
|
+
// ==================== Tenant Operations ====================
|
|
64
|
+
/**
|
|
65
|
+
* Get paginated list of tenants
|
|
66
|
+
* @param params Query parameters for filtering and pagination
|
|
67
|
+
* @returns Promise with paginated tenant response
|
|
68
|
+
*/
|
|
69
|
+
async getTenants(params = {}) {
|
|
70
|
+
return this.restService.request({
|
|
71
|
+
method: "GET",
|
|
72
|
+
url: "/api/saas/tenants",
|
|
73
|
+
params
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get a tenant by ID
|
|
78
|
+
* @param id Tenant ID
|
|
79
|
+
* @returns Promise with tenant data
|
|
80
|
+
*/
|
|
81
|
+
async getTenantById(id) {
|
|
82
|
+
return this.restService.request({
|
|
83
|
+
method: "GET",
|
|
84
|
+
url: `/api/saas/tenants/${id}`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Create a new tenant
|
|
89
|
+
* @param body Tenant creation request
|
|
90
|
+
* @returns Promise with created tenant data
|
|
91
|
+
*/
|
|
92
|
+
async createTenant(body) {
|
|
93
|
+
return this.restService.request({
|
|
94
|
+
method: "POST",
|
|
95
|
+
url: "/api/saas/tenants",
|
|
96
|
+
body
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Update an existing tenant
|
|
101
|
+
* @param body Tenant update request (must include id)
|
|
102
|
+
* @returns Promise with updated tenant data
|
|
103
|
+
*/
|
|
104
|
+
async updateTenant(body) {
|
|
105
|
+
const { id, ...rest } = body;
|
|
106
|
+
return this.restService.request({
|
|
107
|
+
method: "PUT",
|
|
108
|
+
url: `/api/saas/tenants/${id}`,
|
|
109
|
+
body: rest
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Delete a tenant by ID
|
|
114
|
+
* @param id Tenant ID to delete
|
|
115
|
+
* @returns Promise that resolves when deletion is complete
|
|
116
|
+
*/
|
|
117
|
+
async deleteTenant(id) {
|
|
118
|
+
return this.restService.request({
|
|
119
|
+
method: "DELETE",
|
|
120
|
+
url: `/api/saas/tenants/${id}`
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// ==================== Connection String Operations ====================
|
|
124
|
+
/**
|
|
125
|
+
* Get the default connection string for a tenant
|
|
126
|
+
* @param id Tenant ID
|
|
127
|
+
* @returns Promise with connection string (empty string if using shared database)
|
|
128
|
+
*/
|
|
129
|
+
async getDefaultConnectionString(id) {
|
|
130
|
+
return this.restService.request({
|
|
131
|
+
method: "GET",
|
|
132
|
+
url: `/api/saas/tenants/${id}/default-connection-string`,
|
|
133
|
+
responseType: "text"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Update the default connection string for a tenant
|
|
138
|
+
* @param payload Object containing tenant ID and connection string
|
|
139
|
+
* @returns Promise that resolves when update is complete
|
|
140
|
+
*/
|
|
141
|
+
async updateDefaultConnectionString(payload) {
|
|
142
|
+
return this.restService.request({
|
|
143
|
+
method: "PUT",
|
|
144
|
+
url: `/api/saas/tenants/${payload.id}/default-connection-string`,
|
|
145
|
+
params: { defaultConnectionString: payload.defaultConnectionString }
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Delete the default connection string for a tenant (revert to shared database)
|
|
150
|
+
* @param id Tenant ID
|
|
151
|
+
* @returns Promise that resolves when deletion is complete
|
|
152
|
+
*/
|
|
153
|
+
async deleteDefaultConnectionString(id) {
|
|
154
|
+
return this.restService.request({
|
|
155
|
+
method: "DELETE",
|
|
156
|
+
url: `/api/saas/tenants/${id}/default-connection-string`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// ==================== Edition Operations ====================
|
|
160
|
+
/**
|
|
161
|
+
* Get paginated list of editions
|
|
162
|
+
* @param params Query parameters for filtering and pagination
|
|
163
|
+
* @returns Promise with paginated editions response
|
|
164
|
+
*/
|
|
165
|
+
async getEditions(params = {}) {
|
|
166
|
+
return this.restService.request({
|
|
167
|
+
method: "GET",
|
|
168
|
+
url: "/api/saas/editions",
|
|
169
|
+
params
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get an edition by ID
|
|
174
|
+
* @param id Edition ID
|
|
175
|
+
* @returns Promise with edition data
|
|
176
|
+
*/
|
|
177
|
+
async getEditionById(id) {
|
|
178
|
+
return this.restService.request({
|
|
179
|
+
method: "GET",
|
|
180
|
+
url: `/api/saas/editions/${id}`
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Create a new edition
|
|
185
|
+
* @param body Edition creation request
|
|
186
|
+
* @returns Promise with created edition data
|
|
187
|
+
*/
|
|
188
|
+
async createEdition(body) {
|
|
189
|
+
return this.restService.request({
|
|
190
|
+
method: "POST",
|
|
191
|
+
url: "/api/saas/editions",
|
|
192
|
+
body
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Update an existing edition
|
|
197
|
+
* @param body Edition update request (must include id)
|
|
198
|
+
* @returns Promise with updated edition data
|
|
199
|
+
*/
|
|
200
|
+
async updateEdition(body) {
|
|
201
|
+
const { id, ...rest } = body;
|
|
202
|
+
return this.restService.request({
|
|
203
|
+
method: "PUT",
|
|
204
|
+
url: `/api/saas/editions/${id}`,
|
|
205
|
+
body: rest
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Delete an edition by ID
|
|
210
|
+
* @param id Edition ID to delete
|
|
211
|
+
* @returns Promise that resolves when deletion is complete
|
|
212
|
+
*/
|
|
213
|
+
async deleteEdition(id) {
|
|
214
|
+
return this.restService.request({
|
|
215
|
+
method: "DELETE",
|
|
216
|
+
url: `/api/saas/editions/${id}`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// ==================== Statistics Operations ====================
|
|
220
|
+
/**
|
|
221
|
+
* Get usage statistics for editions
|
|
222
|
+
* @returns Promise with usage statistics data
|
|
223
|
+
*/
|
|
224
|
+
async getUsageStatistics() {
|
|
225
|
+
return this.restService.request({
|
|
226
|
+
method: "GET",
|
|
227
|
+
url: "/api/saas/editions/statistics/usage-statistic"
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/hooks/useTenants.ts
|
|
233
|
+
var import_react = require("react");
|
|
234
|
+
var import_core = require("@abpjs/core");
|
|
235
|
+
function useTenants() {
|
|
236
|
+
const restService = (0, import_core.useRestService)();
|
|
237
|
+
const service = (0, import_react.useMemo)(() => new SaasService(restService), [restService]);
|
|
238
|
+
const [tenants, setTenants] = (0, import_react.useState)([]);
|
|
239
|
+
const [totalCount, setTotalCount] = (0, import_react.useState)(0);
|
|
240
|
+
const [selectedTenant, setSelectedTenant] = (0, import_react.useState)(null);
|
|
241
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
242
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
243
|
+
const [sortKey, setSortKey] = (0, import_react.useState)("name");
|
|
244
|
+
const [sortOrder, setSortOrder] = (0, import_react.useState)("");
|
|
245
|
+
const [defaultConnectionString, setDefaultConnectionString] = (0, import_react.useState)("");
|
|
246
|
+
const [useSharedDatabase, setUseSharedDatabase] = (0, import_react.useState)(true);
|
|
247
|
+
const fetchTenants = (0, import_react.useCallback)(
|
|
248
|
+
async (params) => {
|
|
249
|
+
setIsLoading(true);
|
|
250
|
+
setError(null);
|
|
251
|
+
try {
|
|
252
|
+
const response = await service.getTenants(params);
|
|
253
|
+
setTenants(response.items || []);
|
|
254
|
+
setTotalCount(response.totalCount || 0);
|
|
255
|
+
setIsLoading(false);
|
|
256
|
+
return { success: true, data: response };
|
|
257
|
+
} catch (err) {
|
|
258
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch tenants";
|
|
259
|
+
setError(errorMessage);
|
|
260
|
+
setIsLoading(false);
|
|
261
|
+
return { success: false, error: errorMessage };
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
[service]
|
|
265
|
+
);
|
|
266
|
+
const getTenantById = (0, import_react.useCallback)(
|
|
267
|
+
async (id) => {
|
|
268
|
+
setIsLoading(true);
|
|
269
|
+
setError(null);
|
|
270
|
+
try {
|
|
271
|
+
const tenant = await service.getTenantById(id);
|
|
272
|
+
setSelectedTenant(tenant);
|
|
273
|
+
setIsLoading(false);
|
|
274
|
+
return { success: true, data: tenant };
|
|
275
|
+
} catch (err) {
|
|
276
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch tenant";
|
|
277
|
+
setError(errorMessage);
|
|
278
|
+
setIsLoading(false);
|
|
279
|
+
return { success: false, error: errorMessage };
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
[service]
|
|
283
|
+
);
|
|
284
|
+
const createTenant = (0, import_react.useCallback)(
|
|
285
|
+
async (tenant) => {
|
|
286
|
+
setIsLoading(true);
|
|
287
|
+
setError(null);
|
|
288
|
+
try {
|
|
289
|
+
const created = await service.createTenant(tenant);
|
|
290
|
+
setIsLoading(false);
|
|
291
|
+
return { success: true, data: created };
|
|
292
|
+
} catch (err) {
|
|
293
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to create tenant";
|
|
294
|
+
setError(errorMessage);
|
|
295
|
+
setIsLoading(false);
|
|
296
|
+
return { success: false, error: errorMessage };
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
[service]
|
|
300
|
+
);
|
|
301
|
+
const updateTenant = (0, import_react.useCallback)(
|
|
302
|
+
async (tenant) => {
|
|
303
|
+
setIsLoading(true);
|
|
304
|
+
setError(null);
|
|
305
|
+
try {
|
|
306
|
+
const updated = await service.updateTenant(tenant);
|
|
307
|
+
setIsLoading(false);
|
|
308
|
+
return { success: true, data: updated };
|
|
309
|
+
} catch (err) {
|
|
310
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to update tenant";
|
|
311
|
+
setError(errorMessage);
|
|
312
|
+
setIsLoading(false);
|
|
313
|
+
return { success: false, error: errorMessage };
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
[service]
|
|
317
|
+
);
|
|
318
|
+
const deleteTenant = (0, import_react.useCallback)(
|
|
319
|
+
async (id) => {
|
|
320
|
+
setIsLoading(true);
|
|
321
|
+
setError(null);
|
|
322
|
+
try {
|
|
323
|
+
await service.deleteTenant(id);
|
|
324
|
+
setIsLoading(false);
|
|
325
|
+
return { success: true };
|
|
326
|
+
} catch (err) {
|
|
327
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to delete tenant";
|
|
328
|
+
setError(errorMessage);
|
|
329
|
+
setIsLoading(false);
|
|
330
|
+
return { success: false, error: errorMessage };
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
[service]
|
|
334
|
+
);
|
|
335
|
+
const getDefaultConnectionString = (0, import_react.useCallback)(
|
|
336
|
+
async (id) => {
|
|
337
|
+
setError(null);
|
|
338
|
+
try {
|
|
339
|
+
const connString = await service.getDefaultConnectionString(id);
|
|
340
|
+
setDefaultConnectionString(connString || "");
|
|
341
|
+
setUseSharedDatabase(!connString);
|
|
342
|
+
return { success: true, data: connString };
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch connection string";
|
|
345
|
+
setError(errorMessage);
|
|
346
|
+
return { success: false, error: errorMessage };
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
[service]
|
|
350
|
+
);
|
|
351
|
+
const updateDefaultConnectionString = (0, import_react.useCallback)(
|
|
352
|
+
async (payload) => {
|
|
353
|
+
setIsLoading(true);
|
|
354
|
+
setError(null);
|
|
355
|
+
try {
|
|
356
|
+
await service.updateDefaultConnectionString(payload);
|
|
357
|
+
setDefaultConnectionString(payload.defaultConnectionString);
|
|
358
|
+
setUseSharedDatabase(false);
|
|
359
|
+
setIsLoading(false);
|
|
360
|
+
return { success: true };
|
|
361
|
+
} catch (err) {
|
|
362
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to update connection string";
|
|
363
|
+
setError(errorMessage);
|
|
364
|
+
setIsLoading(false);
|
|
365
|
+
return { success: false, error: errorMessage };
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
[service]
|
|
369
|
+
);
|
|
370
|
+
const deleteDefaultConnectionString = (0, import_react.useCallback)(
|
|
371
|
+
async (id) => {
|
|
372
|
+
setIsLoading(true);
|
|
373
|
+
setError(null);
|
|
374
|
+
try {
|
|
375
|
+
await service.deleteDefaultConnectionString(id);
|
|
376
|
+
setDefaultConnectionString("");
|
|
377
|
+
setUseSharedDatabase(true);
|
|
378
|
+
setIsLoading(false);
|
|
379
|
+
return { success: true };
|
|
380
|
+
} catch (err) {
|
|
381
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to delete connection string";
|
|
382
|
+
setError(errorMessage);
|
|
383
|
+
setIsLoading(false);
|
|
384
|
+
return { success: false, error: errorMessage };
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
[service]
|
|
388
|
+
);
|
|
389
|
+
const reset = (0, import_react.useCallback)(() => {
|
|
390
|
+
setTenants([]);
|
|
391
|
+
setTotalCount(0);
|
|
392
|
+
setSelectedTenant(null);
|
|
393
|
+
setIsLoading(false);
|
|
394
|
+
setError(null);
|
|
395
|
+
setSortKey("name");
|
|
396
|
+
setSortOrder("");
|
|
397
|
+
setDefaultConnectionString("");
|
|
398
|
+
setUseSharedDatabase(true);
|
|
399
|
+
}, []);
|
|
400
|
+
return {
|
|
401
|
+
tenants,
|
|
402
|
+
totalCount,
|
|
403
|
+
selectedTenant,
|
|
404
|
+
isLoading,
|
|
405
|
+
error,
|
|
406
|
+
sortKey,
|
|
407
|
+
sortOrder,
|
|
408
|
+
defaultConnectionString,
|
|
409
|
+
useSharedDatabase,
|
|
410
|
+
fetchTenants,
|
|
411
|
+
getTenantById,
|
|
412
|
+
createTenant,
|
|
413
|
+
updateTenant,
|
|
414
|
+
deleteTenant,
|
|
415
|
+
getDefaultConnectionString,
|
|
416
|
+
updateDefaultConnectionString,
|
|
417
|
+
deleteDefaultConnectionString,
|
|
418
|
+
setSelectedTenant,
|
|
419
|
+
setSortKey,
|
|
420
|
+
setSortOrder,
|
|
421
|
+
reset
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/hooks/useEditions.ts
|
|
426
|
+
var import_react2 = require("react");
|
|
427
|
+
var import_core2 = require("@abpjs/core");
|
|
428
|
+
function useEditions() {
|
|
429
|
+
const restService = (0, import_core2.useRestService)();
|
|
430
|
+
const service = (0, import_react2.useMemo)(() => new SaasService(restService), [restService]);
|
|
431
|
+
const [editions, setEditions] = (0, import_react2.useState)([]);
|
|
432
|
+
const [totalCount, setTotalCount] = (0, import_react2.useState)(0);
|
|
433
|
+
const [selectedEdition, setSelectedEdition] = (0, import_react2.useState)(null);
|
|
434
|
+
const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
|
|
435
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
436
|
+
const [sortKey, setSortKey] = (0, import_react2.useState)("displayName");
|
|
437
|
+
const [sortOrder, setSortOrder] = (0, import_react2.useState)("");
|
|
438
|
+
const [usageStatistics, setUsageStatistics] = (0, import_react2.useState)({});
|
|
439
|
+
const fetchEditions = (0, import_react2.useCallback)(
|
|
440
|
+
async (params) => {
|
|
441
|
+
setIsLoading(true);
|
|
442
|
+
setError(null);
|
|
443
|
+
try {
|
|
444
|
+
const response = await service.getEditions(params);
|
|
445
|
+
setEditions(response.items || []);
|
|
446
|
+
setTotalCount(response.totalCount || 0);
|
|
447
|
+
setIsLoading(false);
|
|
448
|
+
return { success: true, data: response };
|
|
449
|
+
} catch (err) {
|
|
450
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch editions";
|
|
451
|
+
setError(errorMessage);
|
|
452
|
+
setIsLoading(false);
|
|
453
|
+
return { success: false, error: errorMessage };
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
[service]
|
|
457
|
+
);
|
|
458
|
+
const getEditionById = (0, import_react2.useCallback)(
|
|
459
|
+
async (id) => {
|
|
460
|
+
setIsLoading(true);
|
|
461
|
+
setError(null);
|
|
462
|
+
try {
|
|
463
|
+
const edition = await service.getEditionById(id);
|
|
464
|
+
setSelectedEdition(edition);
|
|
465
|
+
setIsLoading(false);
|
|
466
|
+
return { success: true, data: edition };
|
|
467
|
+
} catch (err) {
|
|
468
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch edition";
|
|
469
|
+
setError(errorMessage);
|
|
470
|
+
setIsLoading(false);
|
|
471
|
+
return { success: false, error: errorMessage };
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
[service]
|
|
475
|
+
);
|
|
476
|
+
const createEdition = (0, import_react2.useCallback)(
|
|
477
|
+
async (edition) => {
|
|
478
|
+
setIsLoading(true);
|
|
479
|
+
setError(null);
|
|
480
|
+
try {
|
|
481
|
+
const created = await service.createEdition(edition);
|
|
482
|
+
setIsLoading(false);
|
|
483
|
+
return { success: true, data: created };
|
|
484
|
+
} catch (err) {
|
|
485
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to create edition";
|
|
486
|
+
setError(errorMessage);
|
|
487
|
+
setIsLoading(false);
|
|
488
|
+
return { success: false, error: errorMessage };
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
[service]
|
|
492
|
+
);
|
|
493
|
+
const updateEdition = (0, import_react2.useCallback)(
|
|
494
|
+
async (edition) => {
|
|
495
|
+
setIsLoading(true);
|
|
496
|
+
setError(null);
|
|
497
|
+
try {
|
|
498
|
+
const updated = await service.updateEdition(edition);
|
|
499
|
+
setIsLoading(false);
|
|
500
|
+
return { success: true, data: updated };
|
|
501
|
+
} catch (err) {
|
|
502
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to update edition";
|
|
503
|
+
setError(errorMessage);
|
|
504
|
+
setIsLoading(false);
|
|
505
|
+
return { success: false, error: errorMessage };
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
[service]
|
|
509
|
+
);
|
|
510
|
+
const deleteEdition = (0, import_react2.useCallback)(
|
|
511
|
+
async (id) => {
|
|
512
|
+
setIsLoading(true);
|
|
513
|
+
setError(null);
|
|
514
|
+
try {
|
|
515
|
+
await service.deleteEdition(id);
|
|
516
|
+
setIsLoading(false);
|
|
517
|
+
return { success: true };
|
|
518
|
+
} catch (err) {
|
|
519
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to delete edition";
|
|
520
|
+
setError(errorMessage);
|
|
521
|
+
setIsLoading(false);
|
|
522
|
+
return { success: false, error: errorMessage };
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
[service]
|
|
526
|
+
);
|
|
527
|
+
const fetchUsageStatistics = (0, import_react2.useCallback)(
|
|
528
|
+
async () => {
|
|
529
|
+
setError(null);
|
|
530
|
+
try {
|
|
531
|
+
const response = await service.getUsageStatistics();
|
|
532
|
+
setUsageStatistics(response.data || {});
|
|
533
|
+
return { success: true, data: response.data };
|
|
534
|
+
} catch (err) {
|
|
535
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch usage statistics";
|
|
536
|
+
setError(errorMessage);
|
|
537
|
+
return { success: false, error: errorMessage };
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
[service]
|
|
541
|
+
);
|
|
542
|
+
const reset = (0, import_react2.useCallback)(() => {
|
|
543
|
+
setEditions([]);
|
|
544
|
+
setTotalCount(0);
|
|
545
|
+
setSelectedEdition(null);
|
|
546
|
+
setIsLoading(false);
|
|
547
|
+
setError(null);
|
|
548
|
+
setSortKey("displayName");
|
|
549
|
+
setSortOrder("");
|
|
550
|
+
setUsageStatistics({});
|
|
551
|
+
}, []);
|
|
552
|
+
return {
|
|
553
|
+
editions,
|
|
554
|
+
totalCount,
|
|
555
|
+
selectedEdition,
|
|
556
|
+
isLoading,
|
|
557
|
+
error,
|
|
558
|
+
sortKey,
|
|
559
|
+
sortOrder,
|
|
560
|
+
usageStatistics,
|
|
561
|
+
fetchEditions,
|
|
562
|
+
getEditionById,
|
|
563
|
+
createEdition,
|
|
564
|
+
updateEdition,
|
|
565
|
+
deleteEdition,
|
|
566
|
+
fetchUsageStatistics,
|
|
567
|
+
setSelectedEdition,
|
|
568
|
+
setSortKey,
|
|
569
|
+
setSortOrder,
|
|
570
|
+
reset
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/components/Tenants/TenantsComponent.tsx
|
|
575
|
+
var import_react3 = require("react");
|
|
576
|
+
var import_core3 = require("@abpjs/core");
|
|
577
|
+
var import_theme_shared = require("@abpjs/theme-shared");
|
|
578
|
+
var import_react4 = require("@chakra-ui/react");
|
|
579
|
+
var import_react5 = require("@chakra-ui/react");
|
|
580
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
581
|
+
function TenantsComponent({
|
|
582
|
+
onTenantCreated,
|
|
583
|
+
onTenantUpdated,
|
|
584
|
+
onTenantDeleted
|
|
585
|
+
}) {
|
|
586
|
+
const { t } = (0, import_core3.useLocalization)();
|
|
587
|
+
const { warn } = (0, import_theme_shared.useConfirmation)();
|
|
588
|
+
const {
|
|
589
|
+
tenants,
|
|
590
|
+
totalCount,
|
|
591
|
+
selectedTenant,
|
|
592
|
+
isLoading,
|
|
593
|
+
error,
|
|
594
|
+
fetchTenants,
|
|
595
|
+
getTenantById,
|
|
596
|
+
createTenant,
|
|
597
|
+
updateTenant,
|
|
598
|
+
deleteTenant,
|
|
599
|
+
getDefaultConnectionString,
|
|
600
|
+
updateDefaultConnectionString,
|
|
601
|
+
deleteDefaultConnectionString,
|
|
602
|
+
setSelectedTenant
|
|
603
|
+
} = useTenants();
|
|
604
|
+
const { editions, fetchEditions } = useEditions();
|
|
605
|
+
const [modalType, setModalType] = (0, import_react3.useState)(null);
|
|
606
|
+
const [modalVisible, setModalVisible] = (0, import_react3.useState)(false);
|
|
607
|
+
const [modalBusy, setModalBusy] = (0, import_react3.useState)(false);
|
|
608
|
+
const [page, setPage] = (0, import_react3.useState)(0);
|
|
609
|
+
const pageSize = 10;
|
|
610
|
+
const [filter, setFilter] = (0, import_react3.useState)("");
|
|
611
|
+
const [tenantName, setTenantName] = (0, import_react3.useState)("");
|
|
612
|
+
const [tenantEditionId, setTenantEditionId] = (0, import_react3.useState)("");
|
|
613
|
+
const [connStringUseShared, setConnStringUseShared] = (0, import_react3.useState)(true);
|
|
614
|
+
const [connString, setConnString] = (0, import_react3.useState)("");
|
|
615
|
+
(0, import_react3.useEffect)(() => {
|
|
616
|
+
fetchTenants({
|
|
617
|
+
skipCount: page * pageSize,
|
|
618
|
+
maxResultCount: pageSize,
|
|
619
|
+
filter: filter || void 0,
|
|
620
|
+
getEditionNames: true
|
|
621
|
+
});
|
|
622
|
+
}, [page, pageSize, fetchTenants]);
|
|
623
|
+
(0, import_react3.useEffect)(() => {
|
|
624
|
+
fetchEditions();
|
|
625
|
+
}, [fetchEditions]);
|
|
626
|
+
const handleSearch = (0, import_react3.useCallback)(() => {
|
|
627
|
+
setPage(0);
|
|
628
|
+
fetchTenants({
|
|
629
|
+
skipCount: 0,
|
|
630
|
+
maxResultCount: pageSize,
|
|
631
|
+
filter: filter || void 0,
|
|
632
|
+
getEditionNames: true
|
|
633
|
+
});
|
|
634
|
+
}, [filter, pageSize, fetchTenants]);
|
|
635
|
+
const handleAddTenant = (0, import_react3.useCallback)(() => {
|
|
636
|
+
setSelectedTenant(null);
|
|
637
|
+
setTenantName("");
|
|
638
|
+
setTenantEditionId("");
|
|
639
|
+
setModalType("tenant");
|
|
640
|
+
setModalVisible(true);
|
|
641
|
+
}, [setSelectedTenant]);
|
|
642
|
+
const handleEditTenant = (0, import_react3.useCallback)(
|
|
643
|
+
async (id) => {
|
|
644
|
+
const result = await getTenantById(id);
|
|
645
|
+
if (result.success && result.data) {
|
|
646
|
+
setTenantName(result.data.name);
|
|
647
|
+
setTenantEditionId(result.data.editionId || "");
|
|
648
|
+
setModalType("tenant");
|
|
649
|
+
setModalVisible(true);
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
[getTenantById]
|
|
653
|
+
);
|
|
654
|
+
const handleEditConnectionString = (0, import_react3.useCallback)(
|
|
655
|
+
async (id) => {
|
|
656
|
+
const tenantResult = await getTenantById(id);
|
|
657
|
+
if (tenantResult.success) {
|
|
658
|
+
const connResult = await getDefaultConnectionString(id);
|
|
659
|
+
setConnStringUseShared(!connResult.data);
|
|
660
|
+
setConnString(connResult.data || "");
|
|
661
|
+
setModalType("connectionString");
|
|
662
|
+
setModalVisible(true);
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
[getTenantById, getDefaultConnectionString]
|
|
666
|
+
);
|
|
667
|
+
const handleDeleteTenant = (0, import_react3.useCallback)(
|
|
668
|
+
async (tenant) => {
|
|
669
|
+
const status = await warn(
|
|
670
|
+
t("Saas::TenantDeletionConfirmationMessage").replace("{0}", tenant.name),
|
|
671
|
+
t("Saas::AreYouSure")
|
|
672
|
+
);
|
|
673
|
+
if (status === import_theme_shared.Toaster.Status.confirm) {
|
|
674
|
+
const result = await deleteTenant(tenant.id);
|
|
675
|
+
if (result.success) {
|
|
676
|
+
onTenantDeleted?.(tenant.id);
|
|
677
|
+
fetchTenants({
|
|
678
|
+
skipCount: page * pageSize,
|
|
679
|
+
maxResultCount: pageSize,
|
|
680
|
+
filter: filter || void 0,
|
|
681
|
+
getEditionNames: true
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
[warn, t, deleteTenant, onTenantDeleted, fetchTenants, page, pageSize, filter]
|
|
687
|
+
);
|
|
688
|
+
const handleSaveTenant = (0, import_react3.useCallback)(async () => {
|
|
689
|
+
if (!tenantName.trim()) return;
|
|
690
|
+
setModalBusy(true);
|
|
691
|
+
try {
|
|
692
|
+
if (selectedTenant?.id) {
|
|
693
|
+
const result = await updateTenant({
|
|
694
|
+
id: selectedTenant.id,
|
|
695
|
+
name: tenantName,
|
|
696
|
+
editionId: tenantEditionId || void 0,
|
|
697
|
+
concurrencyStamp: selectedTenant.concurrencyStamp
|
|
698
|
+
});
|
|
699
|
+
if (result.success && result.data) {
|
|
700
|
+
onTenantUpdated?.(result.data);
|
|
701
|
+
setModalVisible(false);
|
|
702
|
+
fetchTenants({
|
|
703
|
+
skipCount: page * pageSize,
|
|
704
|
+
maxResultCount: pageSize,
|
|
705
|
+
filter: filter || void 0,
|
|
706
|
+
getEditionNames: true
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
const result = await createTenant({
|
|
711
|
+
name: tenantName,
|
|
712
|
+
editionId: tenantEditionId || void 0
|
|
713
|
+
});
|
|
714
|
+
if (result.success && result.data) {
|
|
715
|
+
onTenantCreated?.(result.data);
|
|
716
|
+
setModalVisible(false);
|
|
717
|
+
fetchTenants({
|
|
718
|
+
skipCount: page * pageSize,
|
|
719
|
+
maxResultCount: pageSize,
|
|
720
|
+
filter: filter || void 0,
|
|
721
|
+
getEditionNames: true
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
} finally {
|
|
726
|
+
setModalBusy(false);
|
|
727
|
+
}
|
|
728
|
+
}, [
|
|
729
|
+
tenantName,
|
|
730
|
+
tenantEditionId,
|
|
731
|
+
selectedTenant,
|
|
732
|
+
updateTenant,
|
|
733
|
+
createTenant,
|
|
734
|
+
onTenantUpdated,
|
|
735
|
+
onTenantCreated,
|
|
736
|
+
fetchTenants,
|
|
737
|
+
page,
|
|
738
|
+
pageSize,
|
|
739
|
+
filter
|
|
740
|
+
]);
|
|
741
|
+
const handleSaveConnectionString = (0, import_react3.useCallback)(async () => {
|
|
742
|
+
if (!selectedTenant?.id) return;
|
|
743
|
+
setModalBusy(true);
|
|
744
|
+
try {
|
|
745
|
+
if (connStringUseShared || !connString.trim()) {
|
|
746
|
+
await deleteDefaultConnectionString(selectedTenant.id);
|
|
747
|
+
} else {
|
|
748
|
+
await updateDefaultConnectionString({
|
|
749
|
+
id: selectedTenant.id,
|
|
750
|
+
defaultConnectionString: connString
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
setModalVisible(false);
|
|
754
|
+
} finally {
|
|
755
|
+
setModalBusy(false);
|
|
756
|
+
}
|
|
757
|
+
}, [
|
|
758
|
+
selectedTenant,
|
|
759
|
+
connStringUseShared,
|
|
760
|
+
connString,
|
|
761
|
+
deleteDefaultConnectionString,
|
|
762
|
+
updateDefaultConnectionString
|
|
763
|
+
]);
|
|
764
|
+
const handleSave = (0, import_react3.useCallback)(() => {
|
|
765
|
+
if (modalType === "tenant") {
|
|
766
|
+
handleSaveTenant();
|
|
767
|
+
} else if (modalType === "connectionString") {
|
|
768
|
+
handleSaveConnectionString();
|
|
769
|
+
}
|
|
770
|
+
}, [modalType, handleSaveTenant, handleSaveConnectionString]);
|
|
771
|
+
const handleCloseModal = (0, import_react3.useCallback)(() => {
|
|
772
|
+
setModalVisible(false);
|
|
773
|
+
setModalType(null);
|
|
774
|
+
setSelectedTenant(null);
|
|
775
|
+
}, [setSelectedTenant]);
|
|
776
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
777
|
+
const getModalTitle = () => {
|
|
778
|
+
if (modalType === "connectionString") {
|
|
779
|
+
return t("Saas::ConnectionStrings");
|
|
780
|
+
}
|
|
781
|
+
return selectedTenant?.id ? t("Saas::Edit") : t("Saas::NewTenant");
|
|
782
|
+
};
|
|
783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Box, { id: "saas-tenants-wrapper", className: "card", children: [
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { justifyContent: "space-between", alignItems: "center", mb: 4, children: [
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { fontSize: "xl", fontWeight: "bold", children: t("Saas::Tenants") }),
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.Button, { colorPalette: "blue", onClick: handleAddTenant, children: t("Saas::NewTenant") })
|
|
787
|
+
] }),
|
|
788
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { gap: 2, mb: 4, children: [
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
790
|
+
import_react4.Input,
|
|
791
|
+
{
|
|
792
|
+
value: filter,
|
|
793
|
+
onChange: (e) => setFilter(e.target.value),
|
|
794
|
+
placeholder: t("AbpUi::PagerSearch"),
|
|
795
|
+
onKeyDown: (e) => e.key === "Enter" && handleSearch()
|
|
796
|
+
}
|
|
797
|
+
),
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.Button, { onClick: handleSearch, children: t("AbpUi::Search") })
|
|
799
|
+
] }),
|
|
800
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Box, { mb: 4, p: 3, bg: "red.100", borderRadius: "md", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { color: "red.800", children: error }) }),
|
|
801
|
+
isLoading && tenants.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Flex, { justifyContent: "center", p: 8, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Spinner, { size: "lg" }) }),
|
|
802
|
+
!isLoading && tenants.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { textAlign: "center", p: 8, color: "gray.500", children: t("Saas::NoTenantsFound") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Box, { borderWidth: "1px", borderRadius: "md", overflow: "hidden", children: [
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Table.Root, { children: [
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.Header, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Table.Row, { children: [
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.ColumnHeader, { children: t("Saas::Actions") }),
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.ColumnHeader, { children: t("Saas::TenantName") }),
|
|
807
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.ColumnHeader, { children: t("Saas::EditionName") })
|
|
808
|
+
] }) }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.Body, { children: tenants.map((tenant) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Table.Row, { children: [
|
|
810
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.Cell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { gap: 1, children: [
|
|
811
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
812
|
+
import_theme_shared.Button,
|
|
813
|
+
{
|
|
814
|
+
size: "sm",
|
|
815
|
+
colorPalette: "blue",
|
|
816
|
+
variant: "outline",
|
|
817
|
+
onClick: () => handleEditTenant(tenant.id),
|
|
818
|
+
children: t("Saas::Edit")
|
|
819
|
+
}
|
|
820
|
+
),
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
822
|
+
import_theme_shared.Button,
|
|
823
|
+
{
|
|
824
|
+
size: "sm",
|
|
825
|
+
variant: "outline",
|
|
826
|
+
onClick: () => handleEditConnectionString(tenant.id),
|
|
827
|
+
children: t("Saas::ConnectionStrings")
|
|
828
|
+
}
|
|
829
|
+
),
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
831
|
+
import_theme_shared.Button,
|
|
832
|
+
{
|
|
833
|
+
size: "sm",
|
|
834
|
+
colorPalette: "red",
|
|
835
|
+
variant: "outline",
|
|
836
|
+
onClick: () => handleDeleteTenant(tenant),
|
|
837
|
+
children: t("Saas::Delete")
|
|
838
|
+
}
|
|
839
|
+
)
|
|
840
|
+
] }) }),
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.Cell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { fontWeight: "medium", children: tenant.name }) }),
|
|
842
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Table.Cell, { children: tenant.editionName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Badge, { colorPalette: "blue", children: tenant.editionName }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { color: "gray.500", children: "-" }) })
|
|
843
|
+
] }, tenant.id)) })
|
|
844
|
+
] }),
|
|
845
|
+
totalCount > pageSize && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { justifyContent: "space-between", alignItems: "center", p: 4, borderTopWidth: "1px", children: [
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.Text, { fontSize: "sm", children: `${page * pageSize + 1} - ${Math.min((page + 1) * pageSize, totalCount)} / ${totalCount}` }),
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { gap: 2, children: [
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
849
|
+
import_theme_shared.Button,
|
|
850
|
+
{
|
|
851
|
+
size: "sm",
|
|
852
|
+
disabled: page === 0,
|
|
853
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
854
|
+
children: t("AbpUi::Previous")
|
|
855
|
+
}
|
|
856
|
+
),
|
|
857
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
858
|
+
import_theme_shared.Button,
|
|
859
|
+
{
|
|
860
|
+
size: "sm",
|
|
861
|
+
disabled: page >= totalPages - 1,
|
|
862
|
+
onClick: () => setPage((p) => p + 1),
|
|
863
|
+
children: t("AbpUi::Next")
|
|
864
|
+
}
|
|
865
|
+
)
|
|
866
|
+
] })
|
|
867
|
+
] })
|
|
868
|
+
] }),
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
870
|
+
import_theme_shared.Modal,
|
|
871
|
+
{
|
|
872
|
+
visible: modalVisible,
|
|
873
|
+
onVisibleChange: setModalVisible,
|
|
874
|
+
header: getModalTitle(),
|
|
875
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { gap: 2, children: [
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.Button, { variant: "outline", onClick: handleCloseModal, children: t("Saas::Cancel") }),
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.Button, { colorPalette: "blue", onClick: handleSave, loading: modalBusy, children: t("AbpIdentity::Save") })
|
|
878
|
+
] }),
|
|
879
|
+
children: [
|
|
880
|
+
modalType === "tenant" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.VStack, { gap: 4, align: "stretch", children: [
|
|
881
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.FormField, { label: t("Saas::TenantName"), required: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
882
|
+
import_react4.Input,
|
|
883
|
+
{
|
|
884
|
+
value: tenantName,
|
|
885
|
+
onChange: (e) => setTenantName(e.target.value),
|
|
886
|
+
placeholder: t("Saas::TenantName"),
|
|
887
|
+
autoFocus: true
|
|
888
|
+
}
|
|
889
|
+
) }),
|
|
890
|
+
editions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.FormField, { label: t("Saas::Edition"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react5.NativeSelectRoot, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
891
|
+
import_react5.NativeSelectField,
|
|
892
|
+
{
|
|
893
|
+
value: tenantEditionId,
|
|
894
|
+
onChange: (e) => setTenantEditionId(e.target.value),
|
|
895
|
+
children: [
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("Saas::SelectEdition") }),
|
|
897
|
+
editions.map((edition) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: edition.id, children: edition.displayName }, edition.id))
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
) }) })
|
|
901
|
+
] }),
|
|
902
|
+
modalType === "connectionString" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.VStack, { gap: 4, align: "stretch", children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react4.Flex, { alignItems: "center", gap: 2, children: [
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
905
|
+
"input",
|
|
906
|
+
{
|
|
907
|
+
type: "checkbox",
|
|
908
|
+
id: "useSharedDatabase",
|
|
909
|
+
checked: connStringUseShared,
|
|
910
|
+
onChange: (e) => setConnStringUseShared(e.target.checked)
|
|
911
|
+
}
|
|
912
|
+
),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "useSharedDatabase", children: t("Saas::DisplayName:UseSharedDatabase") })
|
|
914
|
+
] }),
|
|
915
|
+
!connStringUseShared && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_theme_shared.FormField, { label: t("Saas::DisplayName:DefaultConnectionString"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
916
|
+
import_react4.Input,
|
|
917
|
+
{
|
|
918
|
+
value: connString,
|
|
919
|
+
onChange: (e) => setConnString(e.target.value),
|
|
920
|
+
placeholder: t("Saas::DisplayName:DefaultConnectionString")
|
|
921
|
+
}
|
|
922
|
+
) })
|
|
923
|
+
] })
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
)
|
|
927
|
+
] });
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/components/Editions/EditionsComponent.tsx
|
|
931
|
+
var import_react6 = require("react");
|
|
932
|
+
var import_core4 = require("@abpjs/core");
|
|
933
|
+
var import_theme_shared2 = require("@abpjs/theme-shared");
|
|
934
|
+
var import_react7 = require("@chakra-ui/react");
|
|
935
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
936
|
+
function EditionsComponent({
|
|
937
|
+
onEditionCreated,
|
|
938
|
+
onEditionUpdated,
|
|
939
|
+
onEditionDeleted,
|
|
940
|
+
onManageFeatures
|
|
941
|
+
}) {
|
|
942
|
+
const { t } = (0, import_core4.useLocalization)();
|
|
943
|
+
const { warn } = (0, import_theme_shared2.useConfirmation)();
|
|
944
|
+
const {
|
|
945
|
+
editions,
|
|
946
|
+
totalCount,
|
|
947
|
+
selectedEdition,
|
|
948
|
+
isLoading,
|
|
949
|
+
error,
|
|
950
|
+
fetchEditions,
|
|
951
|
+
getEditionById,
|
|
952
|
+
createEdition,
|
|
953
|
+
updateEdition,
|
|
954
|
+
deleteEdition,
|
|
955
|
+
setSelectedEdition
|
|
956
|
+
} = useEditions();
|
|
957
|
+
const [modalVisible, setModalVisible] = (0, import_react6.useState)(false);
|
|
958
|
+
const [modalBusy, setModalBusy] = (0, import_react6.useState)(false);
|
|
959
|
+
const [page, setPage] = (0, import_react6.useState)(0);
|
|
960
|
+
const pageSize = 10;
|
|
961
|
+
const [filter, setFilter] = (0, import_react6.useState)("");
|
|
962
|
+
const [displayName, setDisplayName] = (0, import_react6.useState)("");
|
|
963
|
+
(0, import_react6.useEffect)(() => {
|
|
964
|
+
fetchEditions({
|
|
965
|
+
skipCount: page * pageSize,
|
|
966
|
+
maxResultCount: pageSize,
|
|
967
|
+
filter: filter || void 0
|
|
968
|
+
});
|
|
969
|
+
}, [page, pageSize, fetchEditions]);
|
|
970
|
+
const handleSearch = (0, import_react6.useCallback)(() => {
|
|
971
|
+
setPage(0);
|
|
972
|
+
fetchEditions({
|
|
973
|
+
skipCount: 0,
|
|
974
|
+
maxResultCount: pageSize,
|
|
975
|
+
filter: filter || void 0
|
|
976
|
+
});
|
|
977
|
+
}, [filter, pageSize, fetchEditions]);
|
|
978
|
+
const handleAddEdition = (0, import_react6.useCallback)(() => {
|
|
979
|
+
setSelectedEdition(null);
|
|
980
|
+
setDisplayName("");
|
|
981
|
+
setModalVisible(true);
|
|
982
|
+
}, [setSelectedEdition]);
|
|
983
|
+
const handleEditEdition = (0, import_react6.useCallback)(
|
|
984
|
+
async (id) => {
|
|
985
|
+
const result = await getEditionById(id);
|
|
986
|
+
if (result.success && result.data) {
|
|
987
|
+
setDisplayName(result.data.displayName);
|
|
988
|
+
setModalVisible(true);
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
[getEditionById]
|
|
992
|
+
);
|
|
993
|
+
const handleDeleteEdition = (0, import_react6.useCallback)(
|
|
994
|
+
async (edition) => {
|
|
995
|
+
const status = await warn(
|
|
996
|
+
t("Saas::EditionDeletionConfirmationMessage").replace("{0}", edition.displayName),
|
|
997
|
+
t("Saas::AreYouSure")
|
|
998
|
+
);
|
|
999
|
+
if (status === import_theme_shared2.Toaster.Status.confirm) {
|
|
1000
|
+
const result = await deleteEdition(edition.id);
|
|
1001
|
+
if (result.success) {
|
|
1002
|
+
onEditionDeleted?.(edition.id);
|
|
1003
|
+
fetchEditions({
|
|
1004
|
+
skipCount: page * pageSize,
|
|
1005
|
+
maxResultCount: pageSize,
|
|
1006
|
+
filter: filter || void 0
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
[warn, t, deleteEdition, onEditionDeleted, fetchEditions, page, pageSize, filter]
|
|
1012
|
+
);
|
|
1013
|
+
const handleManageFeatures = (0, import_react6.useCallback)(
|
|
1014
|
+
(editionId) => {
|
|
1015
|
+
onManageFeatures?.(editionId);
|
|
1016
|
+
},
|
|
1017
|
+
[onManageFeatures]
|
|
1018
|
+
);
|
|
1019
|
+
const handleSave = (0, import_react6.useCallback)(async () => {
|
|
1020
|
+
if (!displayName.trim()) return;
|
|
1021
|
+
setModalBusy(true);
|
|
1022
|
+
try {
|
|
1023
|
+
if (selectedEdition?.id) {
|
|
1024
|
+
const result = await updateEdition({
|
|
1025
|
+
id: selectedEdition.id,
|
|
1026
|
+
displayName,
|
|
1027
|
+
concurrencyStamp: selectedEdition.concurrencyStamp
|
|
1028
|
+
});
|
|
1029
|
+
if (result.success && result.data) {
|
|
1030
|
+
onEditionUpdated?.(result.data);
|
|
1031
|
+
setModalVisible(false);
|
|
1032
|
+
fetchEditions({
|
|
1033
|
+
skipCount: page * pageSize,
|
|
1034
|
+
maxResultCount: pageSize,
|
|
1035
|
+
filter: filter || void 0
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
const result = await createEdition({
|
|
1040
|
+
displayName
|
|
1041
|
+
});
|
|
1042
|
+
if (result.success && result.data) {
|
|
1043
|
+
onEditionCreated?.(result.data);
|
|
1044
|
+
setModalVisible(false);
|
|
1045
|
+
fetchEditions({
|
|
1046
|
+
skipCount: page * pageSize,
|
|
1047
|
+
maxResultCount: pageSize,
|
|
1048
|
+
filter: filter || void 0
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
} finally {
|
|
1053
|
+
setModalBusy(false);
|
|
1054
|
+
}
|
|
1055
|
+
}, [
|
|
1056
|
+
displayName,
|
|
1057
|
+
selectedEdition,
|
|
1058
|
+
updateEdition,
|
|
1059
|
+
createEdition,
|
|
1060
|
+
onEditionUpdated,
|
|
1061
|
+
onEditionCreated,
|
|
1062
|
+
fetchEditions,
|
|
1063
|
+
page,
|
|
1064
|
+
pageSize,
|
|
1065
|
+
filter
|
|
1066
|
+
]);
|
|
1067
|
+
const handleCloseModal = (0, import_react6.useCallback)(() => {
|
|
1068
|
+
setModalVisible(false);
|
|
1069
|
+
setSelectedEdition(null);
|
|
1070
|
+
}, [setSelectedEdition]);
|
|
1071
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
1072
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Box, { id: "saas-editions-wrapper", className: "card", children: [
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { justifyContent: "space-between", alignItems: "center", mb: 4, children: [
|
|
1074
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Text, { fontSize: "xl", fontWeight: "bold", children: t("Saas::Editions") }),
|
|
1075
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared2.Button, { colorPalette: "blue", onClick: handleAddEdition, children: t("Saas::NewEdition") })
|
|
1076
|
+
] }),
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { gap: 2, mb: 4, children: [
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1079
|
+
import_react7.Input,
|
|
1080
|
+
{
|
|
1081
|
+
value: filter,
|
|
1082
|
+
onChange: (e) => setFilter(e.target.value),
|
|
1083
|
+
placeholder: t("AbpUi::PagerSearch"),
|
|
1084
|
+
onKeyDown: (e) => e.key === "Enter" && handleSearch()
|
|
1085
|
+
}
|
|
1086
|
+
),
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared2.Button, { onClick: handleSearch, children: t("AbpUi::Search") })
|
|
1088
|
+
] }),
|
|
1089
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Box, { mb: 4, p: 3, bg: "red.100", borderRadius: "md", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Text, { color: "red.800", children: error }) }),
|
|
1090
|
+
isLoading && editions.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Flex, { justifyContent: "center", p: 8, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Spinner, { size: "lg" }) }),
|
|
1091
|
+
!isLoading && editions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Text, { textAlign: "center", p: 8, color: "gray.500", children: t("Saas::NoEditionsFound") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Box, { borderWidth: "1px", borderRadius: "md", overflow: "hidden", children: [
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Table.Root, { children: [
|
|
1093
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.Header, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Table.Row, { children: [
|
|
1094
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.ColumnHeader, { children: t("Saas::Actions") }),
|
|
1095
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.ColumnHeader, { children: t("Saas::EditionName") })
|
|
1096
|
+
] }) }),
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.Body, { children: editions.map((edition) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Table.Row, { children: [
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.Cell, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { gap: 1, children: [
|
|
1099
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1100
|
+
import_theme_shared2.Button,
|
|
1101
|
+
{
|
|
1102
|
+
size: "sm",
|
|
1103
|
+
colorPalette: "blue",
|
|
1104
|
+
variant: "outline",
|
|
1105
|
+
onClick: () => handleEditEdition(edition.id),
|
|
1106
|
+
children: t("Saas::Edit")
|
|
1107
|
+
}
|
|
1108
|
+
),
|
|
1109
|
+
onManageFeatures && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1110
|
+
import_theme_shared2.Button,
|
|
1111
|
+
{
|
|
1112
|
+
size: "sm",
|
|
1113
|
+
variant: "outline",
|
|
1114
|
+
onClick: () => handleManageFeatures(edition.id),
|
|
1115
|
+
children: t("Saas::Features")
|
|
1116
|
+
}
|
|
1117
|
+
),
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1119
|
+
import_theme_shared2.Button,
|
|
1120
|
+
{
|
|
1121
|
+
size: "sm",
|
|
1122
|
+
colorPalette: "red",
|
|
1123
|
+
variant: "outline",
|
|
1124
|
+
onClick: () => handleDeleteEdition(edition),
|
|
1125
|
+
children: t("Saas::Delete")
|
|
1126
|
+
}
|
|
1127
|
+
)
|
|
1128
|
+
] }) }),
|
|
1129
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Table.Cell, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Text, { fontWeight: "medium", children: edition.displayName }) })
|
|
1130
|
+
] }, edition.id)) })
|
|
1131
|
+
] }),
|
|
1132
|
+
totalCount > pageSize && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { justifyContent: "space-between", alignItems: "center", p: 4, borderTopWidth: "1px", children: [
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.Text, { fontSize: "sm", children: `${page * pageSize + 1} - ${Math.min((page + 1) * pageSize, totalCount)} / ${totalCount}` }),
|
|
1134
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { gap: 2, children: [
|
|
1135
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1136
|
+
import_theme_shared2.Button,
|
|
1137
|
+
{
|
|
1138
|
+
size: "sm",
|
|
1139
|
+
disabled: page === 0,
|
|
1140
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
1141
|
+
children: t("AbpUi::Previous")
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1145
|
+
import_theme_shared2.Button,
|
|
1146
|
+
{
|
|
1147
|
+
size: "sm",
|
|
1148
|
+
disabled: page >= totalPages - 1,
|
|
1149
|
+
onClick: () => setPage((p) => p + 1),
|
|
1150
|
+
children: t("AbpUi::Next")
|
|
1151
|
+
}
|
|
1152
|
+
)
|
|
1153
|
+
] })
|
|
1154
|
+
] })
|
|
1155
|
+
] }),
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1157
|
+
import_theme_shared2.Modal,
|
|
1158
|
+
{
|
|
1159
|
+
visible: modalVisible,
|
|
1160
|
+
onVisibleChange: setModalVisible,
|
|
1161
|
+
header: selectedEdition?.id ? t("Saas::Edit") : t("Saas::NewEdition"),
|
|
1162
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react7.Flex, { gap: 2, children: [
|
|
1163
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared2.Button, { variant: "outline", onClick: handleCloseModal, children: t("Saas::Cancel") }),
|
|
1164
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared2.Button, { colorPalette: "blue", onClick: handleSave, loading: modalBusy, children: t("AbpIdentity::Save") })
|
|
1165
|
+
] }),
|
|
1166
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react7.VStack, { gap: 4, align: "stretch", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared2.FormField, { label: t("Saas::EditionName"), required: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1167
|
+
import_react7.Input,
|
|
1168
|
+
{
|
|
1169
|
+
value: displayName,
|
|
1170
|
+
onChange: (e) => setDisplayName(e.target.value),
|
|
1171
|
+
placeholder: t("Saas::EditionName"),
|
|
1172
|
+
autoFocus: true
|
|
1173
|
+
}
|
|
1174
|
+
) }) })
|
|
1175
|
+
}
|
|
1176
|
+
)
|
|
1177
|
+
] });
|
|
1178
|
+
}
|
|
1179
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1180
|
+
0 && (module.exports = {
|
|
1181
|
+
EditionsComponent,
|
|
1182
|
+
SAAS_ROUTES,
|
|
1183
|
+
SaasService,
|
|
1184
|
+
TenantsComponent,
|
|
1185
|
+
useEditions,
|
|
1186
|
+
useTenants
|
|
1187
|
+
});
|