Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 支持自定义内置环境变量 (admin42) #1492

Merged
merged 23 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.

from django.conf import settings
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from paasng.core.region.models import get_all_regions
from paasng.plat_admin.admin42.serializers.module import ModuleSLZ
from paasng.platform.applications.constants import AppEnvironment
from paasng.platform.engine.configurations.config_var import get_default_builtin_config_vars
from paasng.platform.engine.models.config_var import BuiltinConfigVar
from paasng.platform.engine.serializers import ConfigVarSLZ as BaseConfigVarSLZ
from paasng.utils.validators import RE_CONFIG_VAR_KEY

Expand All @@ -31,3 +37,54 @@ class ConfigVarSLZ(BaseConfigVarSLZ):
required=True,
error_messages={"invalid": _("格式错误,只能以大写字母开头,由大写字母、数字与下划线组成。")},
)


class BuiltinConfigVarCreateInputSLZ(serializers.Serializer):
key = serializers.RegexField(
RE_CONFIG_VAR_KEY,
max_length=128,
required=True,
error_messages={"invalid": _("格式错误,只能以大写字母开头,由大写字母、数字与下划线组成。")},
)
value = serializers.CharField(max_length=512, required=True)
description = serializers.CharField(max_length=512, required=True, help_text="变量描述")

def validate_key(self, key: str) -> str:
if BuiltinConfigVar.objects.filter(key=key).exists():
raise ValidationError(_("内置环境变量 {key} 已存在").format(key=key))

# region 和 environment 不影响默认内置环境变量的 key
region = list(get_all_regions().keys())[0]
lyzqs marked this conversation as resolved.
Show resolved Hide resolved
environment = AppEnvironment.PRODUCTION.value
key_with_prefix = settings.CONFIGVAR_SYSTEM_PREFIX + key
if key_with_prefix in get_default_builtin_config_vars(region, environment):
raise ValidationError(_("名称 {key} 与系统内置变量名冲突").format(key=key_with_prefix))
lyzqs marked this conversation as resolved.
Show resolved Hide resolved

return key


class BuiltinConfigVarCreateOutputSLZ(serializers.Serializer):
lyzqs marked this conversation as resolved.
Show resolved Hide resolved
id = serializers.IntegerField()


class BuiltinConfigVarUpdateInputSLZ(serializers.Serializer):
value = serializers.CharField(max_length=512, required=True)
description = serializers.CharField(max_length=512, required=True, help_text="变量描述")


class BuiltinConfigVarListInputSLZ(serializers.Serializer):
region = serializers.ChoiceField(choices=[(region, region) for region in list(get_all_regions().keys())])

def validate_region(self, region: str) -> str:
if region not in list(get_all_regions().keys()):
raise ValidationError(_("无法找到指定的 region: {region}").format(region=region))
return region


class BuiltinConfigVarListOutputSLZ(serializers.Serializer):
id = serializers.IntegerField()
key = serializers.CharField()
value = serializers.CharField()
description = serializers.CharField()
updated = serializers.DateTimeField()
updater = serializers.CharField()
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ <h3 v-else class="panel-title">$[ panel.name ]</h3>
name: "代码库配置",
url: "{% url 'admin.sourcectl.source_type_spec.manage' %}"
},
{
name: "环境变量管理",
url: "{% url 'admin.builtin_config_var.manage' %}"
},
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@
name: "代码库配置",
icon: 'icon-tree-module-shape',
url: "{% url 'admin.sourcectl.source_type_spec.manage' %}"
},
{
name: "环境变量管理",
icon: 'icon-tree-module-shape',
url: "{% url 'admin.builtin_config_var.manage' %}"
}
]
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
{% extends "admin42/platformmgr/base.html" %}
{% load admin_utils %}
{% block main_content %}
<div id="builtin-config-var-list" class="p20">
<bk-button theme="primary" class="mb20" @click="handleCreate">
新建
</bk-button>

<bk-button class="ml10 mb20" @click="handleShowBuiltinEnv">
显示内置环境变量
</bk-button>

<bk-table :data="data">
<bk-table-column label="Key" prop="key" width="500" :formatter="addPrefix" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="Value" prop="value" width="500" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="描述" prop="description" width="500" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="更新时间" prop="updated" width="250" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="更新者" prop="updater" width="200" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="操作" width="200">
<template slot-scope="props">
<bk-button class="ml5" theme="primary" text @click="handleEdit(props.row)">编辑</bk-button>
<bk-button class="ml5" theme="danger" text @click="handleDelete(props.row)">删除</bk-button>
</template>
</bk-table-column>
</bk-table>

<!-- 创建/编辑环境变量用弹窗 -->
<bk-dialog
v-model="dialog.visible"
header-position="left"
width="800"
:confirm-fn="submitDialog"
@cancel="cancelDialog"
:mask-close="false"
>
<div slot="header">$[ dialog.type === 'create'?'添加':'编辑' ]环境变量</div>
<bk-form ref="form" :label-width="140" :model="dialog.form" :rules="rules">
<bk-form-item label="Key" property="key" :required="dialog.type === 'create'">
<bk-input v-model="dialog.form.key" :disabled="dialog.type !== 'create'">
<template slot="prepend">
<div class="group-text">{{system_prefix}}</div>
</template>
</bk-input>
</bk-form-item>
<bk-form-item label="Value" property="value" :required="true">
<bk-input v-model="dialog.form.value"></bk-input>
</bk-form-item>
<bk-form-item label="描述" property="description" :required="true">
<bk-input v-model="dialog.form.description"></bk-input>
</bk-form-item>
</bk-form>
</bk-dialog>

<!-- 显示默认内置环境变量弹窗 -->
<bk-dialog
v-model="builtinEnvDialog.visible"
header-position="left"
width="1000"
@cancel="cancelBuiltinEnvDialog"
:mask-close="false"
>
<template>
<div>
<bk-radio-group v-model="selectedRegion" @change="changeRegion">
<template v-for="region in regions">
<bk-radio-button :value="region">
$[region]
</bk-radio-button>
</template>
</bk-radio-group>
</div>
lyzqs marked this conversation as resolved.
Show resolved Hide resolved
</template>
<div slot="header">内置环境变量</div>
<bk-table :data="builtinEnvData" class="mt10">
<bk-table-column label="变量" prop="label" width="500" show-overflow-tooltip="true"></bk-table-column>
<bk-table-column label="描述" prop="description" width="400" show-overflow-tooltip="true"></bk-table-column>
lyzqs marked this conversation as resolved.
Show resolved Hide resolved
</bk-table>
</bk-dialog>
</div>
{% endblock %}

{% block main_script %}
<script>
const system_prefix = {{ system_prefix | to_json }}
const regions = {{ regions | to_json }}
const URLRouter = {
create: decodeURI("{% url 'admin.builtin_config_var' %}"),
list: decodeURI("{% url 'admin.builtin_config_var' %}"),
detail: decodeURI("{% url 'admin.builtin_config_var.detail' '${id}' %}"),
}

document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: "#builtin-config-var-list",
delimiters: ['$[', ']'],
data: function () {
return {
data: [],
dialog: {
visible: false,
type: '',
form: {
key: '',
value: '',
description: '',
},
row: undefined
},
deleteConfirm: {
visible: false,
row: undefined
},
rules: {
key: [
{
required: true,
message: '必填项',
trigger: 'blur'
},
],
value: [
{
required: true,
message: '必填项',
trigger: 'blur'
},
],
description: [
{
required: true,
message: '必填项',
trigger: 'blur'
}
],
},
builtinEnvDialog: {
visible: false
},
builtinEnvData: [],
regions: regions,
// 默认选择第一个 region
selectedRegion: regions && regions.length > 0 ? regions[0] : '',
}
},
watch: {
// 当 selectedRegion 改变时,调用 fetchBuiltinEnvData 函数
selectedRegion: 'fetchBuiltinEnvData'
},
methods: {
fetchBuiltinEnvList: async function () {
const el = this.$bkLoading({title: '加载中'})
try {
await this.$http.get(URLRouter.list).then(res => {
this.data = res
})
} finally {
el.hide = true
}
},
cancelDialog: function () {
this.dialog.visible = false
},
submitDialog: async function () {
const url = this.dialog.type === 'create' ? URLRouter.create : this.fillUrlTemplate(URLRouter.detail, {row: this.dialog.row});
let success = true;
const method = this.dialog.type === 'create' ? 'post' : 'put';
try {
await this.$http[method](url, this.dialog.form);
} catch (e) {
success = false;
if (e.response.status === 400) {
this.$bkMessage({
theme: 'error',
message: e.response.data.detail,
})
}
}
if(success) {
this.cancelDialog();
this.fetchBuiltinEnvList();
}
},
handleCreate: function () {
this.dialog.type = "create"
this.dialog.row = undefined
this.dialog.form = {
key: '',
value: '',
description: '',
}
this.$nextTick(() => {
// Clear any previous errors
this.$refs.form.clearError();
});
this.dialog.visible = true
},
handleEdit: function (row) {
this.dialog.type = "edit"
this.dialog.row = row
this.dialog.form = { ...row }
this.$nextTick(() => {
// Clear any previous errors
this.$refs.form.clearError();
});
this.dialog.visible = true
},
handleDelete: function (row) {
this.$bkInfo({
title: '确认要删除?',
confirmLoading: true,
theme: 'danger',
confirmFn: async () => {
try {
await this.deleteRow(row)
this.$bkMessage({
theme: 'success',
message: '删除成功',
})
} catch (e) {
this.$bkMessage({
theme: 'error',
message: e.response.data.detail,
})
}
}
})
},
deleteRow: async function (row) {
const url = this.fillUrlTemplate(URLRouter.detail, { row });
await this.$http.delete(url);

const index = this.data.findIndex(item => item.key === row.key);
if (index !== -1) {
this.data.splice(index, 1);
}
},
fillUrlTemplate: function (url_template, {row}) {
if (!row) {
row = {}
}
return url_template.replace("${id}", row.id)
},
addPrefix: function(row, column, cellValue, index) {
return system_prefix + cellValue
},
handleShowBuiltinEnv: function () {
this.builtinEnvDialog.visible = true;
this.fetchBuiltinEnvData();
},
cancelBuiltinEnvDialog: function () {
this.builtinEnvDialog.visible = false;
},
fetchBuiltinEnvData: async function () {
const baseURL = decodeURI("{% url 'admin.builtin_config_var.builtin' %}");
const url = `${baseURL}?region=${encodeURIComponent(this.selectedRegion)}`;
try {
const res = await this.$http.get(url);
this.convertArray(res);
} catch (e) {
this.$bkMessage({
theme: 'error',
message: e.response.data.detail
});
}
},
convertArray: function (data) {
const list = Object.keys(data).reduce((p, key) => {
if (typeof data[key] === 'string') {
p.push({
label: key,
description: data[key],
});
} else if (typeof data[key] === 'object' && data[key] !== null) {
p.push({
label: `${key}=${data[key].value}`,
description: data[key].description,
});
}
return p;
}, []);
this.builtinEnvData = list;
},
changeRegion: function (region) {
if (this.selectedRegion !== region) {
this.selectedRegion = region;
}
}
},
mounted: function () {
this.fetchBuiltinEnvList()
},
})
})
</script>
{% endblock %}
Loading