Skip to content

Commit f5b554e

Browse files
author
agile.zhou
committed
Merge branch 'master' into publish
2 parents 77aac78 + 414e22c commit f5b554e

13 files changed

Lines changed: 268 additions & 14 deletions

File tree

src/AgileConfig.Server.Apisite/AgileConfig.Server.Apisite.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
6-
<AssemblyVersion>1.11.3</AssemblyVersion>
7-
<Version>1.11.3</Version>
8-
<PackageVersion>1.11.3</PackageVersion>
6+
<AssemblyVersion>1.11.4</AssemblyVersion>
7+
<Version>1.11.4</Version>
8+
<PackageVersion>1.11.4</PackageVersion>
99
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
10-
<FileVersion>1.11.3</FileVersion>
10+
<FileVersion>1.11.4</FileVersion>
1111
<Authors>kklldog</Authors>
1212
<Company>kklldog</Company>
1313
</PropertyGroup>

src/AgileConfig.Server.Apisite/Controllers/AppController.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
45
using System.Threading.Tasks;
56
using AgileConfig.Server.Apisite.Filters;
67
using AgileConfig.Server.Apisite.Models;
@@ -14,6 +15,7 @@
1415
using AgileConfig.Server.IService;
1516
using Microsoft.AspNetCore.Authorization;
1617
using Microsoft.AspNetCore.Mvc;
18+
using Newtonsoft.Json;
1719

1820
namespace AgileConfig.Server.Apisite.Controllers;
1921

@@ -22,14 +24,20 @@ namespace AgileConfig.Server.Apisite.Controllers;
2224
public class AppController : Controller
2325
{
2426
private readonly IAppService _appService;
27+
private readonly IConfigService _configService;
28+
private readonly ISettingService _settingService;
2529
private readonly ITinyEventBus _tinyEventBus;
2630
private readonly IUserService _userService;
2731

2832
public AppController(IAppService appService,
2933
IUserService userService,
34+
IConfigService configService,
35+
ISettingService settingService,
3036
ITinyEventBus tinyEventBus)
3137
{
3238
_userService = userService;
39+
_configService = configService;
40+
_settingService = settingService;
3341
_tinyEventBus = tinyEventBus;
3442
_appService = appService;
3543
}
@@ -107,6 +115,26 @@ private async Task AppendInheritancedInfo(List<AppListVM> list)
107115
}
108116
}
109117

118+
private async Task<bool> IsCurrentUserAdmin(string currentUserId)
119+
{
120+
if (string.IsNullOrWhiteSpace(currentUserId)) return false;
121+
122+
var roles = await _userService.GetUserRolesAsync(currentUserId);
123+
return roles.Any(r => r.Id == SystemRoleConstants.AdminId || r.Id == SystemRoleConstants.SuperAdminId);
124+
}
125+
126+
private async Task<App> GetAuthorizedAppAsync(string appId, string currentUserId, bool isAdmin)
127+
{
128+
var app = await _appService.GetAsync(appId);
129+
if (app == null) return null;
130+
131+
if (isAdmin) return app;
132+
133+
var searchResult = await _appService.SearchAsync(appId, null, null, nameof(App.Id), "ascend", 1, 1,
134+
currentUserId, false);
135+
return searchResult.Apps.FirstOrDefault(x => x.Id == appId);
136+
}
137+
110138
[TypeFilter(typeof(PermissionCheckAttribute), Arguments = new object[] { Functions.App_Add })]
111139
[HttpPost]
112140
public async Task<IActionResult> Add([FromBody] AppVM model)
@@ -273,6 +301,87 @@ public async Task<IActionResult> Delete(string id)
273301
});
274302
}
275303

304+
[TypeFilter(typeof(PermissionCheckAttribute), Arguments = new object[] { Functions.App_Read })]
305+
[HttpPost]
306+
public async Task<IActionResult> Export([FromBody] AppExportRequest model)
307+
{
308+
ArgumentNullException.ThrowIfNull(model);
309+
310+
var appIds = model.AppIds?
311+
.Where(x => !string.IsNullOrWhiteSpace(x))
312+
.Distinct(StringComparer.OrdinalIgnoreCase)
313+
.ToList() ?? new List<string>();
314+
if (!appIds.Any()) throw new ArgumentException("appIds");
315+
316+
var currentUserId = await this.GetCurrentUserId(_userService);
317+
var isAdmin = await IsCurrentUserAdmin(currentUserId);
318+
319+
var apps = new List<App>();
320+
foreach (var appId in appIds)
321+
{
322+
var app = await GetAuthorizedAppAsync(appId, currentUserId, isAdmin);
323+
if (app == null)
324+
{
325+
Response.StatusCode = 403;
326+
return new ContentResult();
327+
}
328+
329+
apps.Add(app);
330+
}
331+
332+
var envs = (await _settingService.GetEnvironmentList())
333+
.Where(x => !string.IsNullOrWhiteSpace(x))
334+
.Distinct(StringComparer.OrdinalIgnoreCase)
335+
.OrderBy(x => x, StringComparer.Ordinal)
336+
.ToList();
337+
338+
var exportFile = new AppExportFileVM
339+
{
340+
ExportedAt = DateTime.UtcNow
341+
};
342+
343+
foreach (var app in apps)
344+
{
345+
var inheritancedApps = await _appService.GetInheritancedAppsAsync(app.Id);
346+
var exportItem = new AppExportItemVM
347+
{
348+
App = new AppExportAppVM
349+
{
350+
Id = app.Id,
351+
Name = app.Name,
352+
Group = app.Group,
353+
Secret = app.Secret,
354+
Enabled = app.Enabled,
355+
Type = (int)app.Type,
356+
Inheritanced = app.Type == AppType.Inheritance,
357+
InheritancedApps = inheritancedApps.Select(x => x.Id).ToList()
358+
}
359+
};
360+
361+
foreach (var env in envs)
362+
{
363+
var configs = await _configService.GetByAppIdAsync(app.Id, env);
364+
exportItem.Envs[env] = configs
365+
.OrderBy(x => x.Group ?? string.Empty, StringComparer.Ordinal)
366+
.ThenBy(x => x.Key ?? string.Empty, StringComparer.Ordinal)
367+
.Select(x => new AppExportConfigVM
368+
{
369+
Group = x.Group,
370+
Key = x.Key,
371+
Value = x.Value,
372+
Description = x.Description
373+
})
374+
.ToList();
375+
}
376+
377+
exportFile.Apps.Add(exportItem);
378+
}
379+
380+
var json = JsonConvert.SerializeObject(exportFile, Formatting.Indented);
381+
var fileName = $"agileconfig-export-{DateTime.UtcNow:yyyyMMddHHmmss}.json";
382+
return File(Encoding.UTF8.GetBytes(json), "application/json", fileName);
383+
}
384+
276385
/// <summary>
277386
/// Get all applications that can be inherited.
278387
/// </summary>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Newtonsoft.Json;
4+
5+
namespace AgileConfig.Server.Apisite.Models;
6+
7+
public class AppExportRequest
8+
{
9+
[JsonProperty("appIds")]
10+
public List<string> AppIds { get; set; }
11+
}
12+
13+
public class AppExportFileVM
14+
{
15+
[JsonProperty("schemaVersion")]
16+
public int SchemaVersion { get; set; } = 1;
17+
18+
[JsonProperty("exportedAt")]
19+
public DateTime ExportedAt { get; set; }
20+
21+
[JsonProperty("apps")]
22+
public List<AppExportItemVM> Apps { get; set; } = new();
23+
}
24+
25+
public class AppExportItemVM
26+
{
27+
[JsonProperty("app")]
28+
public AppExportAppVM App { get; set; }
29+
30+
[JsonProperty("envs")]
31+
public Dictionary<string, List<AppExportConfigVM>> Envs { get; set; } = new();
32+
}
33+
34+
public class AppExportAppVM
35+
{
36+
[JsonProperty("id")]
37+
public string Id { get; set; }
38+
39+
[JsonProperty("name")]
40+
public string Name { get; set; }
41+
42+
[JsonProperty("group")]
43+
public string Group { get; set; }
44+
45+
[JsonProperty("secret")]
46+
public string Secret { get; set; }
47+
48+
[JsonProperty("enabled")]
49+
public bool Enabled { get; set; }
50+
51+
[JsonProperty("type")]
52+
public int Type { get; set; }
53+
54+
[JsonProperty("inheritanced")]
55+
public bool Inheritanced { get; set; }
56+
57+
[JsonProperty("inheritancedApps")]
58+
public List<string> InheritancedApps { get; set; } = new();
59+
}
60+
61+
public class AppExportConfigVM
62+
{
63+
[JsonProperty("group")]
64+
public string Group { get; set; }
65+
66+
[JsonProperty("key")]
67+
public string Key { get; set; }
68+
69+
[JsonProperty("value")]
70+
public string Value { get; set; }
71+
72+
[JsonProperty("description")]
73+
public string Description { get; set; }
74+
}

src/AgileConfig.Server.Data.Abstraction/AgileConfig.Server.Data.Abstraction.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
6+
<Nullable>disable</Nullable>
77
</PropertyGroup>
88

99
<ItemGroup>

src/AgileConfig.Server.Data.Freesql/AgileConfig.Server.Data.Freesql.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Nullable>enable</Nullable>
4+
<Nullable>disable</Nullable>
55
<TargetFramework>net10.0</TargetFramework>
66
</PropertyGroup>
77

src/AgileConfig.Server.Data.Mongodb/AgileConfig.Server.Data.Mongodb.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
6+
<Nullable>disable</Nullable>
77
</PropertyGroup>
88

99
<ItemGroup>

src/AgileConfig.Server.Data.Repository.Mongodb/AgileConfig.Server.Data.Repository.Mongodb.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
6+
<Nullable>disable</Nullable>
77
</PropertyGroup>
88

99
<ItemGroup>

src/AgileConfig.Server.EventHandler/AgileConfig.Server.EventHandler.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
6+
<Nullable>disable</Nullable>
77
</PropertyGroup>
88

99
<ItemGroup>

src/AgileConfig.Server.UI/react-ui-antd/src/pages/Apps/index.tsx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
1+
import { DownloadOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
22
import {
33
ModalForm,
44
ProFormDependency,
@@ -20,7 +20,7 @@ import {
2020
Switch,
2121
Tag,
2222
} from 'antd';
23-
import React, { useState, useRef, useEffect } from 'react';
23+
import React, { Key, useState, useRef, useEffect } from 'react';
2424
import { getIntl, getLocale, Link, useIntl } from 'umi';
2525
import UpdateForm from './comps/updateForm';
2626
import { AppListItem, AppListParams, AppListResult, UserAppAuth } from './data';
@@ -33,6 +33,7 @@ import {
3333
enableOrdisableApp,
3434
saveAppAuth,
3535
getAppGroups,
36+
exportApps,
3637
} from './service';
3738
import UserAuth from './comps/userAuth';
3839
import AuthorizedEle from '@/components/Authorized/AuthorizedElement';
@@ -182,6 +183,38 @@ const handleUserAppAuth = async (model: UserAppAuth) => {
182183
}
183184
};
184185

186+
const buildExportFileName = () => {
187+
const date = new Date();
188+
const pad = (value: number) => value.toString().padStart(2, '0');
189+
return `agileconfig-export-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(
190+
date.getDate(),
191+
)}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}.json`;
192+
};
193+
194+
const handleExportApps = async (apps: AppListItem[]) => {
195+
const intl = getIntl(getLocale());
196+
const hide = message.loading('导出中...');
197+
try {
198+
const file = await exportApps(apps.map((item) => item.id));
199+
hide();
200+
const fileURL = URL.createObjectURL(file);
201+
const a = document.createElement('a');
202+
a.href = fileURL;
203+
a.target = '_blank';
204+
a.download = buildExportFileName();
205+
document.body.appendChild(a);
206+
a.click();
207+
URL.revokeObjectURL(a.href);
208+
document.body.removeChild(a);
209+
message.success('导出成功');
210+
return true;
211+
} catch (error) {
212+
hide();
213+
message.error(intl.formatMessage({ id: 'save_fail' }));
214+
return false;
215+
}
216+
};
217+
185218
const appList: React.FC = (props) => {
186219
const actionRef = useRef<ActionType>();
187220
const addFormRef = useRef<FormInstance>();
@@ -193,6 +226,8 @@ const appList: React.FC = (props) => {
193226
const [userAuthModalVisible, setUserAuthModalVisible] = useState<boolean>(false);
194227
const [currentRow, setCurrentRow] = useState<AppListItem>();
195228
const [dataSource, setDataSource] = useState<AppListResult>();
229+
const [selectedRowKeysState, setSelectedRowKeys] = useState<Key[]>([]);
230+
const [selectedRowsState, setSelectedRows] = useState<AppListItem[]>([]);
196231
const [appGroups, setAppGroups] = useState<{ label: string; value: string }[]>([]);
197232
const [newAppGroupName, setNewAppGroupName] = useState<string>('');
198233
const [appGroupsEnums, setAppGroupsEnums] = useState<{}>({});
@@ -440,6 +475,13 @@ const appList: React.FC = (props) => {
440475
<ProTable
441476
actionRef={actionRef}
442477
options={false}
478+
rowSelection={{
479+
selectedRowKeys: selectedRowKeysState,
480+
onChange: (selectedRowKeys, selectedRows) => {
481+
setSelectedRowKeys(selectedRowKeys);
482+
setSelectedRows(selectedRows as AppListItem[]);
483+
},
484+
}}
443485
search={{
444486
labelWidth: 'auto',
445487
}}
@@ -486,6 +528,22 @@ const appList: React.FC = (props) => {
486528
})}
487529
</Button>
488530
</AuthorizedEle>,
531+
<AuthorizedEle key="1" judgeKey={functionKeys.App_Read}>
532+
<Button
533+
key="export"
534+
icon={<DownloadOutlined />}
535+
disabled={selectedRowsState.length === 0}
536+
onClick={async () => {
537+
const success = await handleExportApps(selectedRowsState);
538+
if (success) {
539+
setSelectedRowKeys([]);
540+
setSelectedRows([]);
541+
}
542+
}}
543+
>
544+
导出
545+
</Button>
546+
</AuthorizedEle>,
489547
];
490548
}}
491549
//dataSource={dataSource}

0 commit comments

Comments
 (0)