Skip to content

Commit 54a867d

Browse files
feat: 应用 ID 保留前缀检测与授权码验证 (#2804)
1 parent 7a3fc6c commit 54a867d

10 files changed

Lines changed: 544 additions & 10 deletions

File tree

webfe/package_vue/src/json/platform-navigation-data.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ export default [
109109
},
110110
sublist: [],
111111
},
112+
{
113+
groupId: 'config',
114+
name: 'authorizationCode',
115+
label: i18n.t('授权码管理'),
116+
iconfontName: 'security',
117+
matchRouters: ['authorizationCode'],
118+
destRoute: {
119+
name: 'authorizationCode',
120+
},
121+
sublist: [],
122+
},
112123
{
113124
groupId: 'user',
114125
name: 'platformUserManagement',

webfe/package_vue/src/language/lang/platform-management/en.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,4 +427,16 @@ export default {
427427
'开启 TLS 后,添加实例时需上传 TLS 相关证书。应用部署后,平台会将 TLS 证书路径写入内置环境变量中。': 'After enabling TLS, TLS-related certificates need to be uploaded when adding instances. After the application is deployed, the platform will write the TLS certificate paths to the built-in environment variables.',
428428
'普通应用暂不支持在此修改,请前往{link}': 'Normal apps are not supported here, please go to {link}',
429429
旧版页面: 'Legacy Page',
430+
'使用 {n} 前缀的应用 ID 需要输入授权码才能创建': 'App IDs with the prefix {n} require an authorization code for creation',
431+
'为了规范应用 ID 命名,特定前缀(如 bk-)仅分配给官方应用,需经平台管理员审核发放授权码。': 'To standardize app ID naming, certain prefixes (e.g., bk-) are reserved for official apps. An authorization code issued by the platform administrator is required.',
432+
'请输入用户申请的应用 ID,如 bk-monitor': 'Please enter the requested app ID, e.g., bk-monitor',
433+
'搜索应用 ID 或授权码': 'Search App ID or authorization code',
434+
'是否删除该授权码?': 'Are you sure you want to delete this authorization code?',
435+
生成授权码: 'Generate Authorization Code',
436+
授权码: 'Authorization Code',
437+
继续生成: 'Generate Another',
438+
已生成授权码: 'Generated Authorization Codes',
439+
生成时间: 'Created At',
440+
已使用: 'Used',
441+
未使用: 'Unused',
430442
};

webfe/package_vue/src/language/lang/platform-management/zh.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,4 +427,16 @@ export default {
427427
'开启 TLS 后,添加实例时需上传 TLS 相关证书。应用部署后,平台会将 TLS 证书路径写入内置环境变量中。': '开启 TLS 后,添加实例时需上传 TLS 相关证书。应用部署后,平台会将 TLS 证书路径写入内置环境变量中。',
428428
'普通应用暂不支持在此修改,请前往{link}': '普通应用暂不支持在此修改,请前往{link}',
429429
旧版页面: '旧版页面',
430+
'使用 {n} 前缀的应用 ID 需要输入授权码才能创建': '使用 {n} 前缀的应用 ID 需要输入授权码才能创建',
431+
'为了规范应用 ID 命名,特定前缀(如 bk-)仅分配给官方应用,需经平台管理员审核发放授权码。': '为了规范应用 ID 命名,特定前缀(如 bk-)仅分配给官方应用,需经平台管理员审核发放授权码。',
432+
'请输入用户申请的应用 ID,如 bk-monitor': '请输入用户申请的应用 ID,如 bk-monitor',
433+
'搜索应用 ID 或授权码': '搜索应用 ID 或授权码',
434+
'是否删除该授权码?': '是否删除该授权码?',
435+
生成授权码: '生成授权码',
436+
授权码: '授权码',
437+
继续生成: '继续生成',
438+
已生成授权码: '已生成授权码',
439+
生成时间: '生成时间',
440+
已使用: '已使用',
441+
未使用: '未使用',
430442
};

webfe/package_vue/src/router/platform.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ const templateConfig = () =>
9898
window.showDeployTip(error);
9999
});
100100

101+
const authorizationCode = () =>
102+
import(/* webpackChunkName: 'platform-config' */ '@/views/platform/authorization-code')
103+
.then((module) => module)
104+
.catch((error) => {
105+
window.showDeployTip(error);
106+
});
107+
101108
export const platformRouters = [
102109
{
103110
path: '/plat-mgt/',
@@ -200,6 +207,14 @@ export const platformRouters = [
200207
title: i18n.t('内置环境变量'),
201208
},
202209
},
210+
{
211+
path: 'authorization-code',
212+
component: authorizationCode,
213+
name: 'authorizationCode',
214+
meta: {
215+
title: i18n.t('授权码管理'),
216+
},
217+
},
203218
{
204219
path: 'apps',
205220
component: platformAppList,

webfe/package_vue/src/store/modules/tenant-config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,5 +205,25 @@ export default {
205205
const url = `${BACKEND_URL}/api/plat_mgt/res_quota_plans/${id}/`;
206206
return http.delete(url);
207207
},
208+
/** 获取保留前缀 */
209+
getReservedPrefixes() {
210+
const url = `${BACKEND_URL}/api/bkapps/applications/creation/reserved_prefixes/`;
211+
return http.get(url);
212+
},
213+
/** 生成授权码 */
214+
generateAuthorizationCode({}, { data }) {
215+
const url = `${BACKEND_URL}/api/plat_mgt/reserved_prefix/`;
216+
return http.post(url, data);
217+
},
218+
/** 获取授权码列表 */
219+
getAuthorizationCodeList() {
220+
const url = `${BACKEND_URL}/api/plat_mgt/reserved_prefix/`;
221+
return http.get(url);
222+
},
223+
/** 删除授权码 */
224+
deleteAuthorizationCode({}, { id }) {
225+
const url = `${BACKEND_URL}/api/plat_mgt/reserved_prefix/${id}/`;
226+
return http.delete(url);
227+
},
208228
},
209229
};

webfe/package_vue/src/views/dev-center/create-app/cloud.vue

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@
4646
? $t('由小写字母组成,长度小于 16 个字符')
4747
: $t('请输入 3-16 字符的小写字母、数字、连字符(-),以小写字母开头')
4848
"
49-
class="form-input-width"
49+
:class="['form-input-width', { 'reserved-prefix-warning': isCodePrefixMatched }]"
5050
></bk-input>
51-
<p
52-
slot="tip"
53-
class="input-tips"
54-
>
51+
<div slot="tip">
5552
{{ $t('应用的唯一标识,创建后不可修改') }}
56-
</p>
53+
<!-- 当前填写与保留前缀冲突,提示用户 -->
54+
<ReservedPrefixTips
55+
ref="reservedPrefixTips"
56+
:code="formData.code"
57+
:reserved-prefixes="reservedPrefixes"
58+
/>
59+
</div>
5760
</bk-form-item>
5861
<bk-form-item
5962
:required="true"
@@ -766,6 +769,7 @@ import ExamplesDirectory from '@/components/examples-directory';
766769
import PlatformCodeRepositoryForm from './comps/platform-code-repository-form.vue';
767770
import CodeSourceSelector from './comps/code-source-selector.vue';
768771
import UnauthorizedTips from './comps/unauthorized-tips.vue';
772+
import ReservedPrefixTips from './comps/reserved-prefix-tips.vue';
769773
770774
export default {
771775
components: {
@@ -780,8 +784,15 @@ export default {
780784
PlatformCodeRepositoryForm,
781785
CodeSourceSelector,
782786
UnauthorizedTips,
787+
ReservedPrefixTips,
783788
},
784789
mixins: [sidebarDiffMixin],
790+
props: {
791+
reservedPrefixes: {
792+
type: Array,
793+
default: () => [],
794+
},
795+
},
785796
data() {
786797
return {
787798
formData: {
@@ -1002,6 +1013,10 @@ export default {
10021013
computed: {
10031014
...mapState(['userFeature', 'platformFeature']),
10041015
...mapGetters(['tenantId', 'isShowTenant']),
1016+
isCodePrefixMatched() {
1017+
if (!this.formData.code || !this.reservedPrefixes.length) return false;
1018+
return this.reservedPrefixes.some((prefix) => this.formData.code.startsWith(prefix));
1019+
},
10051020
curSourceControl() {
10061021
return this.sourceControlTypes.find((item) => item.value === this.sourceControlTypeItem);
10071022
},
@@ -1346,6 +1361,8 @@ export default {
13461361
async handleNext() {
13471362
try {
13481363
await this.$refs.formBaseRef.validate();
1364+
// 校验保留前缀
1365+
await this.$refs?.reservedPrefixTips?.validate();
13491366
await this.$refs?.formImageRef?.validate();
13501367
await this.$refs?.formSourceRef?.validate();
13511368
await this.$refs?.repoInfo?.valid();
@@ -1447,11 +1464,14 @@ export default {
14471464
await this.$refs.formBaseRef.validate();
14481465
}
14491466
this.formLoading = true;
1467+
const authCode = this.$refs.reservedPrefixTips?.getCode() || '';
14501468
const params = {
14511469
is_plugin_app: this.isBkPlugin,
14521470
region: this.regionChoose,
14531471
code: this.formData.code,
14541472
name: this.formData.name,
1473+
// 授权码(前缀冲突时需要)
1474+
...(authCode && { auth_code: authCode }),
14551475
// 租户模式
14561476
...(this.isShowTenant && { app_tenant_mode: this.formData.tenantMode }),
14571477
source_config: {
@@ -1798,4 +1818,9 @@ export default {
17981818
</script>
17991819
<style lang="scss" scoped>
18001820
@import './cloud.scss';
1821+
.reserved-prefix-warning {
1822+
/deep/ .bk-form-input {
1823+
border-color: #f59500 !important;
1824+
}
1825+
}
18011826
</style>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<template>
2+
<div
3+
v-if="matchedPrefixes.length"
4+
class="reserved-prefix-tips"
5+
>
6+
<bk-alert
7+
type="warning"
8+
ext-cls="alert-tip-cls"
9+
>
10+
<div slot="title">
11+
{{ $t('使用 {n} 前缀的应用 ID 需要输入授权码才能创建', { n: matchedPrefixes.join('、') }) }}
12+
<a
13+
class="ml5"
14+
:href="GLOBAL.LINK.BK_APP_DOC + 'topics/paas/app_reserved_prefix'"
15+
target="_blank"
16+
>
17+
{{ $t('查看申请指引') }}
18+
<i class="paasng-icon paasng-jump-link"></i>
19+
</a>
20+
</div>
21+
</bk-alert>
22+
<bk-form
23+
ref="formRef"
24+
:model="formData"
25+
:rules="rules"
26+
class="mt-10"
27+
form-type="vertical"
28+
>
29+
<bk-form-item
30+
property="authCode"
31+
:required="true"
32+
:label="$t('授权码')"
33+
:error-display-type="'normal'"
34+
>
35+
<bk-input
36+
v-model="formData.authCode"
37+
:placeholder="$t('请输入该 ID 对应的 8 位授权码')"
38+
/>
39+
</bk-form-item>
40+
</bk-form>
41+
</div>
42+
</template>
43+
44+
<script>
45+
export default {
46+
props: {
47+
code: {
48+
type: String,
49+
default: '',
50+
},
51+
reservedPrefixes: {
52+
type: Array,
53+
default: () => [],
54+
},
55+
},
56+
data() {
57+
return {
58+
formData: {
59+
authCode: '',
60+
},
61+
rules: {
62+
authCode: [
63+
{
64+
required: true,
65+
message: this.$t('必填项'),
66+
trigger: 'blur',
67+
},
68+
],
69+
},
70+
};
71+
},
72+
computed: {
73+
matchedPrefixes() {
74+
if (!this.code || !this.reservedPrefixes.length) return [];
75+
return this.reservedPrefixes.filter((prefix) => this.code.startsWith(prefix));
76+
},
77+
},
78+
methods: {
79+
/**
80+
* 校验授权码是否已填写
81+
*/
82+
async validate() {
83+
await this.$refs.formRef?.validate();
84+
},
85+
/**
86+
* 获取授权码值
87+
* @returns {string} 授权码
88+
*/
89+
getCode() {
90+
return this.formData.authCode;
91+
},
92+
},
93+
};
94+
</script>
95+
96+
<style lang="scss" scoped>
97+
.reserved-prefix-tips {
98+
margin-top: 12px;
99+
padding: 16px 24px;
100+
background-color: #f5f7fa;
101+
.alert-tip-cls {
102+
/deep/ .paasng-remind {
103+
align-items: flex-start;
104+
}
105+
}
106+
}
107+
</style>

webfe/package_vue/src/views/dev-center/create-app/external.vue

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323
<bk-input
2424
v-model="formData.code"
2525
:placeholder="$t('请输入 3-16 字符的小写字母、数字、连字符(-),以小写字母开头')"
26+
:class="{ 'reserved-prefix-warning': isCodePrefixMatched }"
2627
></bk-input>
27-
<p
28+
<div
2829
class="item-tips mb0"
2930
slot="tip"
3031
>
3132
{{ $t('应用的唯一标识,创建后不可修改') }}
32-
</p>
33+
<ReservedPrefixTips
34+
ref="reservedPrefixTips"
35+
:code="formData.code"
36+
:reserved-prefixes="reservedPrefixes"
37+
/>
38+
</div>
3339
</bk-form-item>
3440
<bk-form-item
3541
:label="$t('应用名称')"
@@ -135,8 +141,18 @@
135141
/* eslint-disable max-len */
136142
import sidebarDiffMixin from '@/mixins/sidebar-diff-mixin';
137143
import { mapGetters } from 'vuex';
144+
import ReservedPrefixTips from './comps/reserved-prefix-tips.vue';
138145
export default {
146+
components: {
147+
ReservedPrefixTips,
148+
},
139149
mixins: [sidebarDiffMixin],
150+
props: {
151+
reservedPrefixes: {
152+
type: Array,
153+
default: () => [],
154+
},
155+
},
140156
data() {
141157
return {
142158
loadingImg: require('@static/images/create-app-loading.svg'),
@@ -203,6 +219,10 @@ export default {
203219
},
204220
computed: {
205221
...mapGetters(['isShowTenant']),
222+
isCodePrefixMatched() {
223+
if (!this.formData.code || !this.reservedPrefixes.length) return false;
224+
return this.reservedPrefixes.some((prefix) => this.formData.code.startsWith(prefix));
225+
},
206226
curUserInfo() {
207227
return this.$store.state.curUserInfo;
208228
},
@@ -226,16 +246,24 @@ export default {
226246
227247
// 处理创建应用
228248
handleCreateApp() {
249+
const validates = [this.$refs.baseInfoForm.validate(), this.$refs.appMarketForm.validate()];
250+
if (this.$refs.reservedPrefixTips) {
251+
validates.push(this.$refs.reservedPrefixTips.validate());
252+
}
229253
// 表单校验
230-
Promise.all([this.$refs.baseInfoForm.validate(), this.$refs.appMarketForm.validate()]).then(
254+
Promise.all(validates).then(
231255
() => {
232256
const params = this.formatParams();
257+
const authCode = this.$refs.reservedPrefixTips?.getCode() || '';
258+
if (authCode) {
259+
params.auth_code = authCode;
260+
}
233261
this.createLoading = true;
234262
this.submitCreateForm(params);
235263
},
236264
(validator) => {
237265
console.error(validator);
238-
},
266+
}
239267
);
240268
},
241269
// 提交表单
@@ -296,6 +324,11 @@ export default {
296324
</script>
297325
<style lang="scss" scoped>
298326
@import './default.scss';
327+
.reserved-prefix-warning {
328+
/deep/ .bk-form-input {
329+
border-color: #f59500 !important;
330+
}
331+
}
299332
.establish {
300333
.external-tip {
301334
margin: 16px 0;

0 commit comments

Comments
 (0)