Commit e689b634 authored by liwei's avatar liwei

Merge remote-tracking branch 'origin/master'

parents 8f6800c6 256d4ab9
......@@ -2,6 +2,12 @@
<PageWrapper class="content-padding" contentBackground @back="goBack">
<template #headerContent>
<div class="modal_top">
<div style="display: flex; gap: 5px; align-items: center">
<span v-if="isEdit === 'false'">查看版本</span>
<Select v-if="isEdit === 'false'" v-model:value="version" style="width: 120px" :options="versionOptions" />
<a-button v-if="isEdit === 'false'">回滚</a-button>
<a-button v-if="isEdit === 'false'" @click="goBack">退出查看</a-button>
</div>
<Icon
icon="ep:arrow-left-bold"
:size="20"
......@@ -17,13 +23,17 @@
<div class="path">数据加载/离线加载</div>
</div>
<div class="buttonGroup">
<a-button v-if="isEdit === 'true'" type="primary" @click="handleOperation"
<a-button :disabled="isEdit !== 'true'" type="primary" @click="handleOperation"
>跳转运维</a-button
>
<a-button v-if="isEdit === 'true'" type="primary" @click="handleSave">保存</a-button>
<a-button v-if="isEdit === 'true'" type="primary" @click="handleDebug">调试</a-button>
<a-button v-if="isEdit === 'true'" type="primary" @click="handleRun">运行</a-button>
<a-button v-if="isEdit === 'true'" type="primary" @click="handlePublish">发布</a-button>
<a-button :disabled="isEdit !== 'true'" type="primary" @click="handleSave">保存</a-button>
<a-button :disabled="isEdit !== 'true'" type="primary" @click="handleDebug"
>调试</a-button
>
<a-button :disabled="isEdit !== 'true'" type="primary" @click="handleRun">运行</a-button>
<a-button :disabled="isEdit !== 'true'" type="primary" @click="handlePublish"
>发布</a-button
>
<a-button type="primary" @click="handleGobalDeply">全局配置</a-button>
<a-button type="primary" @click="handleParameterConfiguration">参数配置</a-button>
<a-button type="primary" @click="handleVersionManagement">版本管理</a-button>
......@@ -38,6 +48,10 @@
<template #getMetadata>
<a-button @click="handleGetMetadata" type="primary">获取元数据</a-button>
</template>
<template #importFile>
<a-button style="margin-right: 5px">下载文件模板</a-button>
<a-button type="primary">导入文件</a-button>
</template>
</BasicForm>
<div style="width: 20%; padding: 0 5px 5px">
<BasicTree
......@@ -69,6 +83,19 @@
</div>
</div>
<BasicForm @register="registerLoadingStrategyForm" style="width: 50%">
<template #incrementalRepresentationColumnAlert>
<Alert
show-icon
style="font-size: 12px"
message="增量标识列只支持日期类型和整数型字段。"
type="info"
/>
</template>
<template #site>
<Checkbox v-model:checked="siteChecked"> 位点 </Checkbox>
<a-input v-if="siteChecked" style="width: 120px; margin-right: 10px" />
<Checkbox v-if="siteChecked">清理位点</Checkbox>
</template>
<template #quantityBasedAlert>
<Alert
show-icon
......@@ -450,6 +477,7 @@
Modal,
RadioGroup,
Radio,
Select,
CheckboxGroup,
Checkbox,
} from 'ant-design-vue';
......@@ -514,6 +542,7 @@
const deleteOtherConfigurationIds = ref([]);
const unfoldAdvancedConfiguration = ref(false);
const unfoldConfigurationTableSelect = ref(false);
const siteChecked = ref(false);
const isEdit = ref(true);
const previewCustomSQL = ref('SELECT * FROM user_info,customer_details,order_history');
let startId = ref();
......@@ -538,7 +567,21 @@
let configurationModeFlag = ref('');
let selectAdvancedConfiguration = ref();
const selectKey = ref('0');
const version = ref('V1');
const versionOptions = [
{
label: 'V1',
value: 'V1',
},
{
label: 'V2',
value: 'V2',
},
{
label: 'V3',
value: 'V4',
},
];
const actionList = [
{
//删除
......@@ -562,6 +605,11 @@
componentProps: ({ formModel, formActionType }) => ({
onChange: () => {
loadType.value = formModel.loadType;
const flag = formModel.loadType === '增量数据加载';
updateSchema([{ field: 'incrementalRepresentationColumn', ifShow: flag }]);
updateSchema([{ field: 'site', ifShow: flag }]);
updateSchema([{ field: 'incrementalRepresentationColumnAlert', ifShow: flag }]);
updateSchema([{ field: 'incrementalRepresentationColumnAlertDivider', ifShow: flag }]);
},
options: [
{ label: '全量数据加载', value: '全量数据加载' },
......@@ -579,6 +627,7 @@
isParsingSQL.value = false;
metadataAcquisitionModeFlag.value = formModel.metadataAcquisitionMode === '自定义SQL';
const flag = !metadataAcquisitionModeFlag.value;
const isImport = formModel.metadataAcquisitionMode === '导入元数据文件';
updateSchema([{ field: 'filterCondition', ifShow: flag && customSQLFlag.value }]);
updateSchema([{ field: 'notCustomSQLTable', ifShow: flag && customSQLFlag.value }]);
updateSchema([{ field: 'customSQL', ifShow: flag }]);
......@@ -593,9 +642,10 @@
]);
updateSchema([{ field: 'sourceDataTableName', ifShow: !flag }]);
updateSchema([{ field: 'isBatchCustomSQLTable', ifShow: !flag && isParsingSQL.value }]);
formActionType.updateSchema([{ field: 'dataBase', ifShow: flag }]);
formActionType.updateSchema([{ field: 'metadataType', ifShow: flag }]);
formActionType.updateSchema([{ field: 'getMetadata', ifShow: flag }]);
formActionType.updateSchema([{ field: 'dataBase', ifShow: flag && !isImport }]);
formActionType.updateSchema([{ field: 'metadataType', ifShow: flag && !isImport }]);
formActionType.updateSchema([{ field: 'getMetadata', ifShow: flag && !isImport }]);
formActionType.updateSchema([{ field: 'importFile', ifShow: flag && isImport }]);
},
options: [
{ label: '从JDBC获取元数据', value: '从JDBC获取元数据' },
......@@ -671,6 +721,12 @@
label: ' ',
slot: 'getMetadata',
},
{
field: 'importFile',
label: ' ',
slot: 'importFile',
ifShow: false,
},
{
field: 'maximumNumberOfDatabaseConnections',
label: '源库最大连接数',
......@@ -682,6 +738,30 @@
];
const LoadingStrategyFormSchema: FormSchema[] = [
{
field: 'incrementalRepresentationColumn',
label: '增量标识列',
component: 'Input',
required: true,
ifShow: false,
},
{
field: 'incrementalRepresentationColumnAlert',
label: ' ',
slot: 'incrementalRepresentationColumnAlert',
ifShow: false,
},
{
field: 'site',
label: ' ',
slot: 'site',
ifShow: false,
},
{
field: 'incrementalRepresentationColumnAlertDivider',
component: 'Divider',
ifShow: false,
},
{
field: 'loadingStrategy',
label: '加载策略',
......@@ -791,7 +871,7 @@
},
{
field: 'sourceDataTableName',
label: '源端数据表名',
label: '源端标识表名',
component: 'Input',
colProps: { lg: 11, md: 11 },
componentProps: {
......@@ -1629,6 +1709,15 @@
router.back();
}
function handleDebug() {
router.push({
path: '/dataIntegration/dataLoading/dataEntryLake/' + 'databaseOfflineLoading',
query: {
isEdit: 'debug',
},
});
}
async function handleSave() {
try {
const isValid = await validateSourceSideConfigurationForm();
......
......@@ -185,7 +185,7 @@ export const cardList = [
{
title: '数据库离线加载',
scene: 'databaseOfflineLoading',
icon: 'iconoir:db',
icon: 'majesticons:data-minus',
color: '#9064e9',
description: '支持数据加载导数据仓库(数据加载功能),也支持数仓将数据写入业务库(卸载功能)',
},
......@@ -196,11 +196,20 @@ export const cardList = [
color: '#9064e9',
description: '支持将文件定期加载入数据仓库中',
},
// {
// title: 'Vben Admin',
// icon: 'logos:vue',
// color: '#1890ff',
// },
{
title: '准实时加载',
scene: 'dataDischargeLake',
icon: 'mdi:database-clock',
color: '#9064e9',
description: '支持从数仓将数据加载到传统关系数据库中,支持目标端(Mysql,Oracle等)。',
},
{
title: '数据出湖',
scene: 'databaseOfflineLoading',
icon: 'icon-park-solid:data-switching',
color: '#9064e9',
description: '支持从数仓将数据加载到传统关系数据库中,支持目标端(Mysql,Oracle等)。',
},
];
export const cardRuleList = [
......
......@@ -24,6 +24,12 @@
<div class="sceneSelectionDescription">
{{ item.description }}
</div>
<div v-if="item.title === '文件离线加载'">
<RadioGroup button-style="solid" v-model:value="modeValue">
<Radio value="Excel文件"> Excel文件 </Radio>
<Radio value="文本文件"> 文本文件 </Radio>
</RadioGroup>
</div>
</Card>
</ListItem>
</Col>
......@@ -37,11 +43,12 @@
import Icon from '@/components/Icon/Icon.vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { cardList } from './mock';
import { Card, Row, Col, List, ListItem } from 'ant-design-vue';
import { Card, Row, Col, List, ListItem, Radio, RadioGroup } from 'ant-design-vue';
import { router } from '@/router';
const emit = defineEmits(['register']);
const rowId = ref('');
const modeValue = ref('Excel文件');
//初始化弹框
const [registerModal, { setModalProps, closeModal }] = useModalInner(async () => {});
......@@ -56,7 +63,8 @@
</script>
<style lang="scss" scoped>
.sceneSelectionCard {
height: 200px;
width: 300px;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
......@@ -79,5 +87,6 @@
color: gray;
text-align: left;
line-height: 1.2;
margin-bottom: 20px;
}
</style>
......@@ -43,7 +43,7 @@
},
{
icon:'ant-design:fund-projection-screen-outlined',
tooltip:'详情',
tooltip:'',
/*label: '详情',*/
onClick: other.bind(null, record),
ifShow: () => {
......@@ -70,7 +70,7 @@
},
{
icon:'ant-design:container-twotone',
tooltip:'API',
tooltip:'',
/*label: 'API',*/
onClick: apiDetail.bind(null, record),
ifShow: () => {
......
......@@ -56,6 +56,7 @@
<script lang="ts" setup>
import { reactive, onMounted } from 'vue';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { getRoleListByPage, deleteById, exportRoleList } from '@/api/system/role/role';
import { PageWrapper } from '@/components/Page';
import { useModal } from '@/components/Modal';
import DataTree from './DataTree.vue';
......@@ -69,6 +70,7 @@
import MoveFile from './handleMove/moveFile.vue';
import importModal from './importModal/importModal.vue';
import { useMessage } from '@/hooks/web/useMessage';
import { downloadByData } from '@/utils/file/download';
defineOptions({ name: 'AccountManagement' });
......@@ -180,7 +182,12 @@
});
}
/** 导出按钮*/
async function handleExport() {}
async function handleExport() {
console.log('导出----');
const params = Object.assign({}, getForm().getFieldsValue());
const data = await exportRoleList(params);
downloadByData(data, 'shell文件' + '.sh');
}
/** 导入成功*/
function handleImportSuccess() {
reload();
......
......@@ -102,6 +102,7 @@
<PreviewModal @register="registerPreviewModal" />
<RecordModal @register="registerRecordModal" />
<SubmitModal @register="registerSubmitModal" />
<versionManagementModal @register="registerVersionManagementModal" />
</PageWrapper>
</template>
<script lang="ts" setup>
......@@ -123,6 +124,7 @@
import RecordModal from './executeRecordModal.vue';
import SubmitModal from './handleSubmit/submitModal.vue';
import { schema } from '@/views/dataIntegration/taskOM/taskOM.data';
import versionManagementModal from './versionManagementModal.vue';
defineOptions({ name: 'AccountManagement' });
......@@ -140,6 +142,7 @@
const [registerSubmitModal, { openModal: openSubmitModal }] = useModal();
const [registerResultModal, { openModal: openResultModal }] = useModal();
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const [registerVersionManagementModal, { openModal: openVersionManagementModal }] = useModal();
const [registerRecordModal, { openModal: openRecordModal }] = useModal();
const [registerForm] = useForm({
labelWidth: 100,
......@@ -174,9 +177,12 @@
title: '提交版本',
});
}
//编辑版本
function handleVersion() {
createMessage.success('编辑版本成功');
openVersionManagementModal(true, {
title: '版本管理',
});
}
function handleChange() {
createMessage.success('格式化成功');
......
import { BasicColumn, FormSchema } from '@/components/Table';
import { DescItem } from '@/components/Description';
export const versionColumns: BasicColumn[] = [
{
title: '版本标题',
dataIndex: 'name',
width: 120,
},
{
title: '上传日期',
dataIndex: 'uploadDate',
width: 120,
},
{
title: '副标题',
dataIndex: 'subTitle',
width: 120,
},
];
export const versionSchema = [
{
name: 'V1',
uploadDate: '2022/01/01 12:24:22',
subTitle: 'v21',
},
{
name: 'V2',
uploadDate: '2022/01/02 13:30:00',
subTitle: 'v22',
},
];
<template>
<BasicModal
width="40%"
v-bind="$attrs"
@register="registerModal"
:title="title"
@ok="handleSubmit"
>
<div style="display: flex">
<div class="w-full">
<BasicTable @register="registerTable">
<template #toolbar> </template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'ant-design:eye-outlined',
tooltip: '查看详情',
onClick: handleDetail,
},
{
icon: 'ant-design:edit-outlined',
tooltip: '编辑',
onClick: modEdit,
},
{
icon: 'ant-design:rollback-outlined',
tooltip: '回退版本',
popConfirm: {
title: '是否确认回退版本',
confirm: handleVersionRollback,
},
},
]"
/>
</template>
</template>
</BasicTable>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { BasicModal, useModal, useModalInner } from '@/components/Modal';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { versionColumns, versionSchema } from './version.data';
import { message } from 'ant-design-vue';
const title = ref();
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false });
title.value = data.title;
});
const [registerTable] = useTable({
dataSource: versionSchema,
columns: versionColumns,
actionColumn: {
width: 120,
title: '操作',
dataIndex: 'action',
},
showIndexColumn: false,
});
function handleDetail() {
console.log('查看详情');
closeModal();
}
function modEdit() {
console.log('编辑');
closeModal();
}
function handleVersionRollback() {
message.success('回退成功');
closeModal();
}
async function handleSubmit() {
closeModal();
}
onMounted(() => {});
</script>
<template>
<PageWrapper>
</PageWrapper>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" scoped>
</style>
import {BasicColumn, FormSchema} from "@/components/Table";
/** 列表展示字段*/
export const columns: BasicColumn[] = [
{
title: '名称',
dataIndex: 'name',
width: 150,
},
{
title: '任务数',
dataIndex: 'taskCount',
width: 60,
},
{
title: '组别',
dataIndex: 'groupName',
width: 220,
},
{
title: '状态',
dataIndex: 'flag',
width: 80,
slots: { customRender: 'flag' },
},
{
title: '运行环境路径',
dataIndex: 'runtimeEnvironmentPath',
width: 200,
},
];
/** 列表筛选项*/
export const searchFormSchema: FormSchema[] = [
{
field: 'name',
label: '',
component: 'InputSearch',
colProps: { span: 20},
componentProps: {
placeholder: '输入关键字搜索',
onChange: (e: any) => {
console.log(e);
},
},
},
];
/** 左上*/
export const groupSchema: BasicColumn[] = [
{
title: '',
width: 150,
dataIndex: 'name',
slots: { customRender: 'name' },
},
{
title: '',
width: 30,
dataIndex: 'number',
},
];
/** 左下*/
export const pathSchema: BasicColumn[] = [
{
title: '',
width: 150,
dataIndex: 'name',
},
];
<template>
<PageWrapper contentBackground dense contentClass="flex">
<template #headerContent>
<div style="border-bottom: 2px solid #EBEFF6;">
<p style="margin: -20px 0 10px 0;color: #DCDCDC; ">调度/执行器管理/</p>
<div style="display: flex;align-items: center;margin-bottom:10px;">
<div>
<Icon icon="ant-design:apartment-outlined" :size="25" style="color:#5689E5;"/>
</div>
<span style="font-size: 20px">执行器编辑器</span>
</div>
<div type="primary" class="save-button" @click="resourceReplacement">
<a-button type="text" style="color: #169aff;border-color:#169aff;">执行器资源替换
</a-button>
</div>
<div type="primary" class="save-button1" @click="downloadAndInstallPackage">
<a-button type="text" style="color: #169aff;border-color:#169aff;">下载执行器rpm安装包
</a-button>
</div>
</div>
</template>
<div style="display:flex;">
<div style="width: 20%;border-right: 2px solid #EBEFF6;">
<div>
<BasicTable @register="registerGroupTable" :searchInfo="searchInfo">
<template #tableTitle>
<div style="display: flex;align-items: center">
<div class="container ">
<span class="executor-text">组别</span>
</div>
</div>
</template>
<template #toolbar>
<Icon icon="ant-design:plus-outlined"
:size="20" @click="Delete"/>
<Icon icon="ant-design:clear-outlined"
:size="20" @click="Delete"/>
</template>
</BasicTable>
</div>
<div>
<BasicTable @register="registerPathTable" :searchInfo="searchInfo">
<template #tableTitle>
<div style="display: flex;align-items: center">
<div class="container1">
<span class="executor-text">运行环境路径</span>
</div>
</div>
</template>
<template #toolbar>
<Icon icon="ant-design:plus-outlined"
:size="20" @click="Delete"/>
<Icon icon="ant-design:clear-outlined"
:size="20" @click="Delete"/>
</template>
</BasicTable>
</div>
</div>
<div style="width: 80%">
<BasicTable @register="registerTable" :searchInfo="searchInfo">
<template #tableTitle>
<div style="display: flex;align-items: center">
<div class="container ">
<span class="executor-text">执行器</span>
</div>
<BasicForm @register="registerForm" style="margin-bottom: -20px"/>
</div>
</template>
<template #toolbar>
<a-button :disabled="getRowSelection().selectedRowKeys <=0"
type="primary" @click="addEnvironmentPath">
<Icon icon="ant-design:plus-outlined" :size="15"/>
添加运行环境路径
</a-button>
<a-button :disabled="getRowSelection().selectedRowKeys <=0"
type="primary" @click="addGroup">添加到组别
</a-button>
<a-button :disabled="getRowSelection().selectedRowKeys <=0"
type="primary" @click="Delete">
<Icon icon="ant-design:clear-outlined"></Icon>
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
label: '添加到组别',
onClick: addGroup.bind(null, record),
},
{
icon:'ant-design:clear-outlined',
/*label: '删除',*/
type:'text',
onClick: Delete.bind(null, record),
},
]"
/>
</template>
</template>
<template #flag="{ text }">
<span v-if="text === '正常'">
<Icon icon="ant-design:check-circle-filled" :size="15" style="color:#82BD9D;"/>
正常
</span>
<span v-else-if="text === '不正常'">
<Icon icon="ant-design:close-circle-filled" :size="15" style="color:#C02C20;"/>
不正常
</span>
</template>
</BasicTable>
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import {PageWrapper} from "@/components/Page";
import Icon from "@/components/Icon/Icon.vue";
import {groupTable, pathTable, tableList} from './mock';
import {columns, groupSchema, pathSchema, searchFormSchema} from './executorManagement.data';
import {BasicTable, useTable, TableAction} from '@/components/Table';
import {reactive} from "vue";
import {Divider} from "ant-design-vue";
import BasicForm from "@/components/Form/src/BasicForm.vue";
const searchInfo = reactive<Recordable>({});
const [registerTable, {
reload,
updateTableDataRecord,
getSearchInfo,
getForm,
getRowSelection
}] = useTable({
/* title: '执行器',*/
api: async (params) => {
const response = {
pageNu: "1",
pageSize: "10",
pages: "1",
total: tableList.length,
code: '',
message: '',
data: tableList,
};
return {...response};
},
rowKey: 'businessId',
rowSelection: true,
showIndexColumn: false,
columns,
/* formConfig: {
schemas: searchFormSchema,
//在 input 中输入时按回车自动提交
autoSubmitOnEnter: true,
//是否显示操作按钮(重置/提交)
showActionButtonGroup: false,
baseRowStyle: {
/!*position: 'absolute',*!/
margin: '0 0 -20px 0',
}
},
useSearchForm: true,*/
showTableSetting: false,
bordered: true,
handleSearchInfoFn(info) {
console.log('handleSearchInfoFn', info);
return info;
},
actionColumn: {
width: 180,
title: '操作',
dataIndex: 'action',
// slots: { customRender: 'action' },
},
});
/**右上*/
const [registerForm] = useTable({
schemas: searchFormSchema,
//在 input 中输入时按回车自动提交
autoSubmitOnEnter: true,
//是否显示操作按钮(重置/提交)
showActionButtonGroup: false,
rowSelection: true,
pagination: false,
showIndexColumn: false,
scroll: {y: 300},
showSummary: true,
});
/**左上*/
const [registerGroupTable, {}] = useTable({
rowKey: 'pathId',
title: '组别',
dataSource: groupTable,
columns: groupSchema,
rowSelection: true,
pagination: false,
showIndexColumn: false,
scroll: {y: 300},
showSummary: true,
});
/**左下*/
const [registerPathTable] = useTable({
title: '运行环境路径',
rowKey: 'groupId',
dataSource: pathTable,
columns: pathSchema,
rowSelection: true,
pagination: false,
showIndexColumn: false,
scroll: {y: 300},
showSummary: true,
});
/**执行器资源替换*/
function resourceReplacement() {
}
/**搜索*/
function search() {
}
/**下载执行器rpm安装包*/
function downloadAndInstallPackage() {
}
/**添加运行环境路径*/
function addEnvironmentPath() {
}
/**添加到组别*/
function addGroup() {
}
/**删除Delete*/
function Delete() {
console.log('打印加号按钮')
}
</script>
<style lang="scss" scoped>
.save-button {
position: absolute; /* 绝对定位 */
top: 15px;
right: 10px;
margin: 25px 200px 10px 10px;
}
.save-button1 {
position: absolute;
top: 15px;
right: 10px;
margin: 25px 10px 10px 10px;
}
.container {
width: 70px;
display: flex;
justify-content: center;
align-items: center; /* 让内容垂直居中 */
}
.container1 {
width: 120px;
display: flex;
justify-content: center;
align-items: center; /* 让内容垂直居中 */
}
.executor-text {
display: inline-flex; /* 保持内联样式 */
align-items: center; /* 确保文本垂直居中 */
}
.executor-text::before {
content: ''; /* 伪元素的内容为空 */
display: inline-block;
width: 4px; /* 伪元素的宽度 */
height: 20px; /* 伪元素的高度 */
background-color:#5689E5; /* 蓝色 */
margin-right: 5px; /* 与文字的间距 */
}
</style>
/**表格数据*/
export const tableList: any[] = [
{
businessId: 1,
name: 'ceshiceschiceshciec',
taskCount: '0',
groupName: 'default group',
flag: '正常',
runtimeEnvironmentPath: '',
},
{
businessId: 2,
name: 'fafafafafafafafafaf',
taskCount: '1',
groupName: 'default group,agent_executor',
flag: '正常',
runtimeEnvironmentPath: 'java_path.python3',
},
];
/**左上表格数据*/
export const groupTable: any[] = [
{
groupId: 1,
name: 'default group',
number:'2',
},
{
groupId: 2,
name: 'agent_executor',
number:'1',
},
];
/**左下表格数据*/
export const pathTable: any[] = [
{
pathId: 1,
name: 'java_path',
},
{
pathId: 2,
name: 'python3',
},
];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment