This is an automated email from the ASF dual-hosted git repository.
benjobs pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git
The following commit(s) were added to refs/heads/dev by this push:
new a0453e66e feat: add spark home (#3977)
a0453e66e is described below
commit a0453e66ef21a7fe4b19486d0ffe955da6f10c0b
Author: Kriszu <[email protected]>
AuthorDate: Wed Aug 21 08:05:28 2024 +0800
feat: add spark home (#3977)
---
.../src/api/spark/home.ts | 87 +++++++++++
.../src/api/spark/home.type.ts | 37 +++++
.../src/enums/sparkEnum.ts | 7 +
.../src/locales/lang/en/spark/home.ts | 45 ++++++
.../src/locales/lang/zh-CN/spark/home.ts | 43 +++++
.../src/views/spark/app.vue | 26 +++
.../src/views/spark/components/Modal.vue | 172 ++++++++++++++++++++
.../src/views/spark/components/index.ts | 17 ++
.../src/views/spark/home.vue | 174 +++++++++++++++++++++
9 files changed, 608 insertions(+)
diff --git a/streampark-console/streampark-console-webapp/src/api/spark/home.ts
b/streampark-console/streampark-console-webapp/src/api/spark/home.ts
new file mode 100644
index 000000000..5ff145196
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/api/spark/home.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { SparkCreate, SparkEnv } from './home.type';
+import { defHttp } from '/@/utils/http/axios';
+
+enum FLINK_API {
+ LIST = '/spark/env/list',
+ CHECK = '/spark/env/check',
+ CREATE = '/spark/env/create',
+ UPDATE = '/spark/env/update',
+ DELETE = '/spark/env/delete',
+ DEFAULT = '/spark/env/default',
+}
+/**
+ * spark environment data
+ * @returns Promise<SparkEnv[]>
+ */
+export function fetchSparkEnvList() {
+ return defHttp.post<SparkEnv[]>({
+ url: FLINK_API.LIST,
+ });
+}
+
+/**
+ * Set the default
+ * @param {String} id
+ */
+export function fetchSetDefault(id: string) {
+ return defHttp.post({
+ url: FLINK_API.DEFAULT,
+ data: { id },
+ });
+}
+
+/**
+ * delete flink env
+ * @param {String} id
+ */
+export function fetchSparkEnvRemove(id: string) {
+ return defHttp.post({
+ url: FLINK_API.DELETE,
+ data: { id },
+ });
+}
+
+/**
+ * Check if the environment exists
+ * @param {Recordable} data
+ */
+export function fetchSparkEnvCheck(data: {
+ id: string | null;
+ sparkName: string;
+ sparkHome: string;
+}) {
+ return defHttp.post({ url: FLINK_API.CHECK, data });
+}
+
+/**
+ * Create spark
+ *
+ */
+export function fetchSparkEnvCreate(data: SparkCreate) {
+ return defHttp.post({ url: FLINK_API.CREATE, data }, { isTransformResponse:
false });
+}
+
+/**
+ * update spark
+ * @param data
+ */
+export function fetchSparkEnvUpdate(data: SparkCreate) {
+ return defHttp.post({ url: FLINK_API.UPDATE, data }, { isTransformResponse:
false });
+}
diff --git
a/streampark-console/streampark-console-webapp/src/api/spark/home.type.ts
b/streampark-console/streampark-console-webapp/src/api/spark/home.type.ts
new file mode 100644
index 000000000..fa9dfc5dd
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/api/spark/home.type.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// flink home data
+export interface SparkEnv {
+ id: string;
+ sparkName: string;
+ sparkHome: string;
+ sparkConf: string;
+ description: string;
+ scalaVersion: string;
+ version: string;
+ isDefault: boolean;
+ createTime: string;
+ streamParkScalaVersion: string;
+ versionOfMiddle?: any;
+}
+
+export interface SparkCreate {
+ id?: string | null;
+ flinkName: string;
+ flinkHome: string;
+ description: string;
+}
diff --git
a/streampark-console/streampark-console-webapp/src/enums/sparkEnum.ts
b/streampark-console/streampark-console-webapp/src/enums/sparkEnum.ts
new file mode 100644
index 000000000..96e1d180a
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/enums/sparkEnum.ts
@@ -0,0 +1,7 @@
+export enum SparkEnvCheckEnum {
+ INVALID_PATH = -1,
+ OK = 0,
+ NAME_REPEATED = 1,
+ SPARK_DIST_NOT_FOUND = 2,
+ SPARK_DIST_REPEATED = 3,
+}
diff --git
a/streampark-console/streampark-console-webapp/src/locales/lang/en/spark/home.ts
b/streampark-console/streampark-console-webapp/src/locales/lang/en/spark/home.ts
new file mode 100644
index 000000000..878e55e6f
--- /dev/null
+++
b/streampark-console/streampark-console-webapp/src/locales/lang/en/spark/home.ts
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default {
+ title: 'Spark Home',
+ tips: {
+ remove: 'The current spark home has been successfully deleted.',
+ setDefault: 'Successfully set the default spark home.',
+ sparkName: 'Spark alias, for example: Spark-1.12',
+ sparkHome:
+ 'The absolute path of the server where Spark is located, for example:
/usr/local/spark',
+ sparkNameIsRequired: 'Spark name is required',
+ sparkHomeIsRequired: 'Spark Home is required',
+ sparkNameIsRepeated: 'Spark name already exists',
+ sparkHomePathIsInvalid: 'Spark Home path is invalid',
+ sparkDistNotFound: 'spark-dist jar file not found in spark/lib path',
+ sparkDistIsRepeated:
+ 'Multiple spark-dist jar files exist in spark/lib path, there must be
only one!',
+ createSparkHomeSuccessful: 'Creation successful!',
+ updateSparkHomeSuccessful: 'Update successful!',
+ },
+ form: {
+ sparkName: 'Spark Name',
+ sparkHome: 'Installation Path',
+ description: 'Description',
+ },
+ placeholder: {
+ sparkName: 'Please enter Spark alias',
+ sparkHome: 'Please enter Spark installation path',
+ description: 'Spark description',
+ },
+};
diff --git
a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/spark/home.ts
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/spark/home.ts
new file mode 100644
index 000000000..1f18dc9c3
--- /dev/null
+++
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/spark/home.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default {
+ title: 'Spark Home',
+ tips: {
+ remove: '当前的 spark home 已被成功删除。',
+ setDefault: '成功设置默认spark home',
+ sparkName: 'Spark别名,举例: Spark-1.12',
+ sparkHome: 'Spark所在服务器的绝对路径,举例: /usr/local/spark',
+ sparkNameIsRequired: 'Spark名称必填',
+ sparkHomeIsRequired: 'Spark Home 不能为空',
+ sparkNameIsRepeated: 'Spark名称已存在',
+ sparkHomePathIsInvalid: 'Spark Home路径无效',
+ sparkDistNotFound: 'spark/lib 路径下未找到 spark-dist jar文件',
+ sparkDistIsRepeated: 'spark/lib 路径下存在多个spark-dist jar文件, 必须只能有一个!',
+ createSparkHomeSuccessful: '创建成功!',
+ updateSparkHomeSuccessful: '更新成功!',
+ },
+ form: {
+ sparkName: 'Spark 名称',
+ sparkHome: '安装路径',
+ description: '描述',
+ },
+ placeholder: {
+ sparkName: '请输入Spark别名',
+ sparkHome: '请输入Spark安装路径',
+ description: 'Spark描述',
+ },
+};
diff --git
a/streampark-console/streampark-console-webapp/src/views/spark/app.vue
b/streampark-console/streampark-console-webapp/src/views/spark/app.vue
new file mode 100644
index 000000000..67aa94dd8
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/spark/app.vue
@@ -0,0 +1,26 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<script lang="ts" setup>
+ defineOptions({
+ name: 'SparkApplication',
+ });
+</script>
+<template>
+ <PageWrapper contentFullHeight>
+ <div>Coming soon</div>
+ </PageWrapper>
+</template>
diff --git
a/streampark-console/streampark-console-webapp/src/views/spark/components/Modal.vue
b/streampark-console/streampark-console-webapp/src/views/spark/components/Modal.vue
new file mode 100644
index 000000000..4e0aa1d2c
--- /dev/null
+++
b/streampark-console/streampark-console-webapp/src/views/spark/components/Modal.vue
@@ -0,0 +1,172 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<script lang="ts" setup>
+ import { useI18n } from '/@/hooks/web/useI18n';
+ import { h, ref } from 'vue';
+ import { BasicForm, useForm } from '/@/components/Form';
+ import { SvgIcon } from '/@/components/Icon';
+ import { BasicModal, useModalInner } from '/@/components/Modal';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ import { SparkEnvCheckEnum } from '/@/enums/sparkEnum';
+ import { fetchSparkEnvCheck, fetchSparkEnvUpdate } from '/@/api/spark/home';
+ import { fetchSparkEnvCreate } from '/@/api/spark/home';
+
+ defineOptions({
+ name: 'FlinkModal',
+ });
+ const emit = defineEmits(['reload', 'register']);
+ const versionId = ref<string | null>(null);
+ const { t } = useI18n();
+ const { Swal } = useMessage();
+ const [registerForm, { setFieldsValue, validate, resetFields }] = useForm({
+ labelWidth: 120,
+ colon: true,
+ showActionButtonGroup: false,
+ labelCol: { lg: 7, sm: 7 },
+ wrapperCol: { lg: 16, sm: 4 },
+ baseColProps: { span: 24 },
+ schemas: [
+ {
+ field: 'sparkName',
+ label: t('spark.home.form.sparkName'),
+ component: 'Input',
+ componentProps: {
+ placeholder: t('spark.home.placeholder.sparkName'),
+ allowClear: true,
+ },
+ afterItem: () => h('span', { class: 'tip-info' },
t('spark.home.tips.sparkName')),
+ rules: [{ required: true, message:
t('spark.home.tips.sparkNameIsRequired') }],
+ },
+ {
+ field: 'sparkHome',
+ label: t('spark.home.form.sparkHome'),
+ component: 'Input',
+ componentProps: {
+ placeholder: t('spark.home.placeholder.sparkHome'),
+ allowClear: true,
+ },
+ afterItem: () => h('span', { class: 'tip-info' },
t('spark.home.tips.sparkHome')),
+ rules: [{ required: true, message:
t('spark.home.tips.sparkHomeIsRequired') }],
+ },
+ {
+ field: 'description',
+ label: t('spark.home.form.description'),
+ component: 'InputTextArea',
+ componentProps: {
+ placeholder: t('spark.home.placeholder.description'),
+ allowClear: true,
+ },
+ },
+ ],
+ });
+ const [registerModalInner, { changeOkLoading, closeModal }] =
useModalInner(async (data) => {
+ resetFields();
+ if (data) {
+ versionId.value = data.versionId;
+ setFieldsValue(data);
+ }
+ });
+
+ /* form submit */
+ async function handleSubmit() {
+ changeOkLoading(true);
+ let formValue;
+ try {
+ formValue = await validate();
+ } catch (error) {
+ console.warn('validate error:', error);
+ return;
+ } finally {
+ changeOkLoading(false);
+ }
+ // Detection environment
+ const { data: resp } = await fetchSparkEnvCheck({
+ id: versionId.value,
+ sparkName: formValue.sparkName,
+ sparkHome: formValue.sparkHome,
+ });
+ const checkResp = parseInt(resp.data);
+ if (checkResp !== SparkEnvCheckEnum.OK) {
+ switch (checkResp) {
+ case SparkEnvCheckEnum.INVALID_PATH:
+ Swal.fire('Failed', t('spark.home.tips.sparkHomePathIsInvalid'),
'error');
+ break;
+ case SparkEnvCheckEnum.NAME_REPEATED:
+ Swal.fire('Failed', t('spark.home.tips.sparkNameIsRepeated'),
'error');
+ break;
+ case SparkEnvCheckEnum.SPARK_DIST_NOT_FOUND:
+ Swal.fire('Failed', t('spark.home.tips.sparkDistNotFound'), 'error');
+ break;
+ case SparkEnvCheckEnum.SPARK_DIST_REPEATED:
+ Swal.fire('Failed', t('spark.home.tips.sparkDistIsRepeated'),
'error');
+ break;
+ }
+ changeOkLoading(false);
+ return;
+ }
+
+ try {
+ let message: string;
+ let success = false;
+ // create
+ if (versionId.value == null) {
+ const res = await fetchSparkEnvCreate(formValue);
+ if (res.data) {
+ success = true;
+ message =
formValue.flinkName.concat(t('spark.home.tips.createSparkHomeSuccessful'));
+ } else {
+ message = res.message;
+ }
+ } else {
+ // update
+ const res = await fetchSparkEnvUpdate({
+ id: versionId.value,
+ ...formValue,
+ });
+ if (res.data) {
+ message =
formValue.flinkName.concat(t('spark.home.tips.updateSparkHomeSuccessful'));
+ success = true;
+ } else {
+ message = res.message;
+ }
+ }
+ if (success) {
+ Swal.fire({
+ icon: 'success',
+ title: message,
+ showConfirmButton: false,
+ timer: 2000,
+ });
+ closeModal();
+ emit('reload');
+ } else {
+ Swal.fire('Failed', message.replaceAll(/\[StreamPark]/g, ''), 'error');
+ }
+ } finally {
+ changeOkLoading(false);
+ }
+ }
+</script>
+<template>
+ <BasicModal @register="registerModalInner" v-bind="$attrs"
@ok="handleSubmit">
+ <template #title>
+ <SvgIcon name="spark" />
+ {{ t('common.add') }}
+ </template>
+ <BasicForm @register="registerForm" />
+ </BasicModal>
+</template>
diff --git
a/streampark-console/streampark-console-webapp/src/views/spark/components/index.ts
b/streampark-console/streampark-console-webapp/src/views/spark/components/index.ts
new file mode 100644
index 000000000..fc6df83d1
--- /dev/null
+++
b/streampark-console/streampark-console-webapp/src/views/spark/components/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { default as SparkEnvModal } from './Modal.vue';
diff --git
a/streampark-console/streampark-console-webapp/src/views/spark/home.vue
b/streampark-console/streampark-console-webapp/src/views/spark/home.vue
new file mode 100644
index 000000000..94b7fe88a
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/spark/home.vue
@@ -0,0 +1,174 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<script lang="ts" setup>
+ import { useI18n } from '/@/hooks/web/useI18n';
+ import { onMounted, ref } from 'vue';
+ import { useModal } from '/@/components/Modal';
+ import { SvgIcon } from '/@/components/Icon';
+ import { List, Switch, Card, Popconfirm, Tooltip } from 'ant-design-vue';
+ import {
+ CheckOutlined,
+ CloseOutlined,
+ DeleteOutlined,
+ EditOutlined,
+ PlusOutlined,
+ } from '@ant-design/icons-vue';
+ import { SparkEnvModal } from './components';
+ import { useMessage } from '/@/hooks/web/useMessage';
+ import { PageWrapper } from '/@/components/Page';
+ import { BasicTitle } from '/@/components/Basic';
+ import { fetchSetDefault, fetchSparkEnvList, fetchSparkEnvRemove } from
'/@/api/spark/home';
+ import { SparkEnv } from '/@/api/spark/home.type';
+
+ defineOptions({
+ name: 'SparkEnvSetting',
+ });
+
+ const { t } = useI18n();
+ const versionId = ref<string | null>(null);
+ const { Swal, createMessage } = useMessage();
+ const sparkEnvs = ref<SparkEnv[]>([]);
+ const [registerModal, { openModal: openFlinkModal }] = useModal();
+ /* Edit button */
+ async function handleEditSpark(item: SparkEnv) {
+ versionId.value = item.id;
+ openFlinkModal(true, {
+ versionId: item.id,
+ sparkName: item.sparkName,
+ sparkHome: item.sparkHome,
+ description: item.description || null,
+ });
+ }
+
+ /* delete spark home */
+ async function handleDelete(item: SparkEnv) {
+ await fetchSparkEnvRemove(item.id);
+ await getSparkEnv();
+ createMessage.success(t('spark.home.tips.remove'));
+ }
+
+ /* set as default environment */
+ async function handleSetDefault(item: SparkEnv) {
+ if (item.isDefault) {
+ await fetchSetDefault(item.id);
+ Swal.fire({
+ icon: 'success',
+ title: item.sparkName.concat(t('spark.home.tips.setDefault')),
+ showConfirmButton: false,
+ timer: 2000,
+ });
+ getSparkEnv();
+ }
+ }
+
+ /* Get spark environment data */
+ async function getSparkEnv() {
+ sparkEnvs.value = await fetchSparkEnvList();
+ }
+
+ onMounted(() => {
+ getSparkEnv();
+ });
+</script>
+<template>
+ <PageWrapper contentFullHeight>
+ <Card :bordered="false">
+ <BasicTitle>{{ t('spark.home.title') }}</BasicTitle>
+ <div>
+ <a-button
+ type="dashed"
+ style="width: 100%; margin-top: 20px"
+ @click="openFlinkModal(true, {})"
+ >
+ <PlusOutlined />
+ {{ t('common.add') }}
+ </a-button>
+ </div>
+ <List>
+ <List.Item v-for="(item, index) in sparkEnvs" :key="index">
+ <List.Item.Meta
+ style="width: 60%"
+ :title="item.sparkName"
+ :description="item.description"
+ >
+ <template #avatar>
+ <SvgIcon class="avatar p-15px" name="spark" size="60" />
+ </template>
+ </List.Item.Meta>
+
+ <div class="list-content flex" style="width: 40%">
+ <div class="list-content-item" style="width: 60%">
+ <span>{{ t('spark.home.title') }}</span>
+ <p style="margin-top: 10px">
+ {{ item.sparkHome }}
+ </p>
+ </div>
+ <div class="list-content-item">
+ <span>Default</span>
+ <p style="margin-top: 10px">
+ <Switch
+ :disabled="item.isDefault"
+ @click="handleSetDefault(item)"
+ v-model:checked="item.isDefault"
+ >
+ <template #checkedChildren>
+ <CheckOutlined />
+ </template>
+ <template #unCheckedChildren>
+ <CloseOutlined />
+ </template>
+ </Switch>
+ </p>
+ </div>
+ </div>
+
+ <template #actions>
+ <Tooltip :title="t('common.edit')">
+ <a-button
+ @click="handleEditSpark(item)"
+ shape="circle"
+ size="large"
+ class="control-button"
+ >
+ <EditOutlined />
+ </a-button>
+ </Tooltip>
+ <Popconfirm
+ :title="t('common.delText')"
+ :cancel-text="t('common.no')"
+ :ok-text="t('common.yes')"
+ @confirm="handleDelete(item)"
+ >
+ <a-button
+ :disabled="item.isDefault && sparkEnvs.length > 1"
+ type="danger"
+ shape="circle"
+ size="large"
+ class="control-button"
+ >
+ <DeleteOutlined />
+ </a-button>
+ </Popconfirm>
+ </template>
+ </List.Item>
+ </List>
+ </Card>
+
+ <SparkEnvModal @register="registerModal" @reload="getSparkEnv" />
+ </PageWrapper>
+</template>
+<style lang="less"></style>