|
- /** Copyright 2020 Tianshu AI Platform. All Rights Reserved.
- *
- * Licensed 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
- *
- * http://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.
- * =============================================================
- */
-
- <template>
- <div class="app-container">
- <!--工具栏-->
- <div class="head-container">
- <cdOperation :addProps="operationProps">
- <template v-if="hasPermission('training:image:editDefault')" #left>
- <el-button type="primary" round class="filter-item" @click="doSetDefaultImage">
- Notebook默认镜像设置
- </el-button>
- </template>
- <span slot="right">
- <el-input
- v-model="localQuery.imageNameOrId"
- clearable
- placeholder="请输入镜像名称或ID"
- class="filter-item"
- style="width: 200px;"
- @keyup.enter.native="crud.toQuery"
- @clear="crud.toQuery"
- />
- <rrOperation @resetQuery="resetQuery" />
- </span>
- </cdOperation>
- </div>
- <div class="list-head">
- <el-tabs v-model="active" class="eltabs-inlineblock" @tab-click="handleClick">
- <el-tab-pane id="tab_0" label="我的镜像" :name="IMAGE_RESOURCE_ENUM.CUSTOM" />
- <el-tab-pane id="tab_1" label="预置镜像" :name="IMAGE_RESOURCE_ENUM.PRESET" />
- </el-tabs>
- </div>
- <!--表格渲染-->
- <el-table
- v-if="prefabricate"
- ref="table"
- v-loading="crud.loading"
- :data="crud.data"
- highlight-current-row
- @selection-change="crud.selectionChangeHandler"
- @sort-change="crud.sortChange"
- >
- <el-table-column v-if="!isPreset" prop="id" label="ID" sortable="custom" width="80px" />
- <el-table-column prop="imageName" label="镜像名称" sortable="custom" />
- <el-table-column prop="imageTag" label="镜像版本号" sortable="custom" />
- <el-table-column prop="imageTypes" label="镜像用途" width="180px">
- <template #header>
- <dropdown-header
- title="镜像用途"
- :list="imageTypesList"
- :filtered="Boolean(localQuery.imageTypes)"
- @command="filterImageTypes"
- />
- </template>
- <template slot-scope="scope">
- <div>{{ getImageTypes(scope.row.imageTypes) }}</div>
- </template>
- </el-table-column>
- <el-table-column prop="remark" label="镜像描述" show-overflow-tooltip />
- <el-table-column prop="createTime" label="上传时间" sortable="custom" width="200px">
- <template slot-scope="scope">
- <span>{{ parseTime(scope.row.createTime) }}</span>
- </template>
- </el-table-column>
- <el-table-column v-if="isAdmin || isCustom" label="操作" width="200px" fixed="right">
- <template slot-scope="scope">
- <el-button
- v-if="(hasPermission('training:image:edit') && isCustom) || (isPreset && isAdmin)"
- :id="`doEdit_` + scope.$index"
- type="text"
- @click.stop="doEdit(scope.row)"
- >
- 编辑
- </el-button>
- <el-button
- v-if="hasPermission('training:image:delete') && (!isPreset || isAdmin)"
- :id="`doDelete_` + scope.$index"
- type="text"
- @click.stop="doDelete(scope.row.id)"
- >
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- <!--分页组件-->
- <pagination />
- <!--表单组件-->
- <BaseModal
- :before-close="crud.cancelCU"
- :visible="crud.status.cu > 0"
- :title="crud.status.title"
- :loading="crud.status.cu === 2"
- width="600px"
- @open="onDialogOpen"
- @cancel="crud.cancelCU"
- @ok="crud.submitCU"
- >
- <el-form ref="form" :model="form" :rules="rules" label-width="120px">
- <el-form-item v-if="isFormAdd && isAdmin" label="镜像类别" prop="imageResource">
- <el-radio-group v-model="form.imageResource" @change="imageResourceChange">
- <el-radio :label="Number(IMAGE_RESOURCE_ENUM.CUSTOM)" border class="mr-0"
- >我的镜像</el-radio
- >
- <el-radio :label="Number(IMAGE_RESOURCE_ENUM.PRESET)" border>预置镜像</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="镜像名称" prop="imageName">
- <el-autocomplete
- ref="imageName"
- v-model="form.imageName"
- class="inline-input w-400"
- :fetch-suggestions="querySearchAsync"
- placeholder="请选择或输入镜像名称"
- ></el-autocomplete>
- </el-form-item>
- <el-form-item label="镜像用途">
- <el-select
- v-model="form.imageTypes"
- placeholder="请选择或输入镜像用途"
- class="w-400"
- multiple
- filterable
- allow-create
- default-first-option
- >
- <el-option
- v-for="item in imageTypesList.slice(1)"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item ref="imageUrl" label="镜像地址" prop="imageUrl">
- <el-input v-model="form.imageUrl" placeholder="请输入镜像地址" class="w-400" />
- <BaseTooltip icon="el-icon-warning" class="c-info">
- <template #content>
- <div>
- 镜像地址标准格式:镜像仓库域名/命名空间/镜像名:镜像版本号<br />示例:registry.cn-hangzhou.aliyuncs.com/enlin/notebook:v1
- </div>
- </template>
- </BaseTooltip>
- </el-form-item>
- <el-form-item label="镜像版本号" prop="imageTag">
- <el-input id="imageTag" v-model="form.imageTag" class="w-400" />
- </el-form-item>
- <el-form-item label="描述" prop="remark">
- <el-input
- id="remark"
- v-model="form.remark"
- type="textarea"
- :rows="4"
- maxlength="1024"
- show-word-limit
- placeholder
- class="w-400"
- />
- </el-form-item>
- </el-form>
- </BaseModal>
- <!--Notebook默认镜像设置表单-->
- <BaseModal
- :visible.sync="notebookFormVisible"
- title="Notebook默认镜像设置"
- :loading="notebookFormSubmitting"
- width="800px"
- @cancel="notebookFormVisible = false"
- @ok="onSubmitNotebookForm"
- >
- <el-form
- ref="noteBookFormRef"
- :model="noteBookForm"
- :rules="noteBookRules"
- label-width="120px"
- @submit.native.prevent
- >
- <el-form-item label="默认镜像" prop="defaultTag">
- <el-select
- id="defaultImage"
- v-model="noteBookForm.defaultImage"
- placeholder="请选择镜像"
- class="w-400"
- filterable
- allow-create
- default-first-option
- @change="getNoteBookTags"
- >
- <el-option v-for="item in noteBookImages" :key="item" :label="item" :value="item" />
- </el-select>
- <el-select
- id="defaultTag"
- v-model="noteBookForm.id"
- placeholder="请选择镜像版本"
- style="width: 200px;"
- filterable
- >
- <el-option
- v-for="(item, index) in noteBookTags"
- :key="index"
- :label="item.imageTag"
- :value="item.id"
- />
- </el-select>
- </el-form-item>
- </el-form>
- </BaseModal>
- </div>
- </template>
-
- <script>
- import { mapGetters } from 'vuex';
-
- import cdOperation from '@crud/CD.operation';
- import rrOperation from '@crud/RR.operation';
- import pagination from '@crud/Pagination';
- import CRUD, { presenter, header, form, crud } from '@crud/crud';
- import trainingImageApi, {
- del,
- setDefaultImage,
- getDefaultImage,
- getImageNameList,
- getImageTagList,
- } from '@/api/trainingImage/index';
- import {
- ADMIN_ROLE_ID,
- hasPermission,
- validateImageName,
- validateImageTag,
- IMAGE_TYPE_ENUM,
- IMAGE_TYPE_MAP,
- } from '@/utils';
- import BaseModal from '@/components/BaseModal';
- import BaseTooltip from '@/components/BaseTooltip';
- import DropdownHeader from '@/components/DropdownHeader';
- import { imageConfig } from '@/config';
-
- import { IMAGE_RESOURCE_ENUM } from '../trainingJob/utils';
-
- const defaultForm = {
- imageUrl: null,
- imageTag: null,
- remark: null,
- imageTypes: Number(IMAGE_TYPE_ENUM),
- imageResource: Number(IMAGE_RESOURCE_ENUM.CUSTOM),
- imageName: null,
- };
-
- const defaultQuery = {
- imageNameOrId: null,
- imageTypes: null,
- };
-
- export default {
- name: 'TrainingImage',
- components: {
- BaseModal,
- BaseTooltip,
- pagination,
- cdOperation,
- rrOperation,
- DropdownHeader,
- },
- cruds() {
- return CRUD({
- title: '镜像',
- crudMethod: { ...trainingImageApi },
- optShow: {
- add: imageConfig.allowUploadImage && hasPermission('training:image:save'),
- del: false,
- },
- queryOnPresenterCreated: false,
- props: {
- optText: {
- add: '创建镜像',
- },
- optTitle: {
- add: '创建',
- },
- },
- });
- },
- mixins: [presenter(), header(), form(defaultForm), crud()],
- data() {
- return {
- active: IMAGE_RESOURCE_ENUM.CUSTOM,
- localQuery: { ...defaultQuery },
- rules: {
- imageTypes: [{ required: true, message: '请选择镜像类型', trigger: 'change' }],
- imageResource: [{ required: true, message: '请选择镜像来源', trigger: 'change' }],
- imageName: [
- { required: true, message: '请选择项目名称', trigger: 'change' },
- { validator: validateImageName, trigger: ['blur', 'change'] },
- ],
- imageUrl: [{ required: true, message: '请输入镜像地址', trigger: ['blur', 'manual'] }],
- imageTag: [
- { required: true, message: '请输入镜像版本号', trigger: 'blur' },
- { validator: validateImageTag, trigger: ['blur', 'change'] },
- ],
- },
- noteBookRules: {
- defaultImage: [{ required: true, message: '请选择默认镜像', trigger: 'blur' }],
- id: [{ required: true, message: '请选择默认镜像版本', trigger: 'blur' }],
- },
- harborProjectList: [],
- prefabricate: true,
- // 以下为配置参数及常量参数
- IMAGE_RESOURCE_ENUM,
- // 设置notebook默认镜像相关参数
- noteBookImages: [],
- noteBookTags: [],
- noteBookForm: { defaultImage: '', defaultTag: '', id: '' },
-
- formType: 'add',
- notebookFormVisible: false,
- notebookFormSubmitting: false,
- };
- },
- computed: {
- ...mapGetters(['user', 'isAdmin']),
- rolePermissions() {
- const { roles } = this.user;
- return roles && roles.length && roles[0].id === ADMIN_ROLE_ID;
- },
- isCustom() {
- return this.active === IMAGE_RESOURCE_ENUM.CUSTOM;
- },
- isPreset() {
- return this.active === IMAGE_RESOURCE_ENUM.PRESET;
- },
- disableAdd() {
- if (this.isAdmin) return false; // 管理员可以创建我的镜像和预置镜像
- return !this.isCustom; // 其他角色只有在我的镜像处可以点击上传
- },
- operationProps() {
- return {
- disabled: this.disableAdd,
- };
- },
- imageTypesList() {
- const arr = [{ label: '全部', value: null }];
- for (const key in IMAGE_TYPE_MAP) {
- arr.push({ label: IMAGE_TYPE_MAP[key], value: +key });
- }
- return arr;
- },
- isFormAdd() {
- return this.formType === 'add';
- },
- },
- mounted() {
- this.crud.refresh();
- },
- methods: {
- hasPermission,
- getImageTypes(imageTypes) {
- return imageTypes.map((type) => IMAGE_TYPE_MAP[type]).join(',');
- },
- getNoteBookTags() {
- this.noteBookForm.defaultTag = null;
- this.noteBookForm.id = null;
- if (!this.noteBookForm.defaultImage) {
- this.noteBookImages = [];
- return Promise.reject();
- }
- return getImageTagList({
- imageName: this.noteBookForm.defaultImage,
- imageTypes: IMAGE_TYPE_ENUM.NOTEBOOK,
- imageResource: Number(IMAGE_RESOURCE_ENUM.PRESET),
- }).then((res) => {
- this.noteBookTags = res;
- });
- },
- // handle
- handleClick() {
- this.localQuery = { ...defaultQuery };
- this.crud.toQuery();
- // 切换tab键时让表格重渲
- this.prefabricate = false;
- this.$nextTick(() => {
- this.prefabricate = true;
- });
- },
- // hook
- [CRUD.HOOK.beforeToAdd]() {
- this.formType = 'add';
- if (this.isPreset) {
- this.form.imageResource = Number(IMAGE_RESOURCE_ENUM.PRESET);
- }
- },
- [CRUD.HOOK.beforeRefresh]() {
- this.crud.query = { ...this.localQuery };
- switch (this.active) {
- case IMAGE_RESOURCE_ENUM.CUSTOM:
- case IMAGE_RESOURCE_ENUM.PRESET:
- this.crud.query.imageTypes = this.localQuery.imageTypes;
- this.crud.query.imageResource = Number(this.active);
- break;
- // no default
- }
- },
- [CRUD.HOOK.beforeToEdit]() {
- this.formType = 'edit';
- },
- async querySearchAsync(queryString, cb) {
- let { harborProjectList } = this;
- harborProjectList = harborProjectList.map((item) => {
- return { value: item };
- });
- const results = queryString
- ? harborProjectList.filter(this.createFilter(queryString))
- : harborProjectList;
- cb(results);
- },
- createFilter(queryString) {
- return (harborProject) => {
- return harborProject.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
- };
- },
- async getImageNameList() {
- this.harborProjectList = await getImageNameList({
- imageResource: this.form.imageResource,
- });
- },
- imageResourceChange() {
- this.getImageNameList();
- },
- onImageTypeChange() {
- this.form.imageName = null;
- this.form.imageResource = Number(IMAGE_RESOURCE_ENUM.CUSTOM);
- this.getImageNameList();
- },
- async onDialogOpen() {
- this.getImageNameList();
- },
- filterImageTypes(imageTypes) {
- this.localQuery.imageTypes = imageTypes;
- this.crud.toQuery();
- },
- resetQuery() {
- this.localQuery = { ...defaultQuery };
- },
- async doEdit(imageObj) {
- const dataObj = {
- ids: [imageObj.id],
- ...imageObj,
- };
- await this.crud.toEdit(dataObj);
- },
- doDelete(id) {
- this.$confirm('此操作将永久删除该镜像, 是否继续?', '请确认').then(async () => {
- await del({ ids: [id] });
- this.$message({
- message: '删除成功',
- type: 'success',
- });
- this.crud.refresh();
- });
- },
-
- async doSetDefaultImage() {
- // 获取默认镜像
- const defaultImage = await getDefaultImage();
- this.noteBookForm.defaultImage = defaultImage.length ? defaultImage[0].imageName : '';
- this.noteBookForm.defaultTag = defaultImage.length ? defaultImage[0].imageTag : '';
- this.noteBookForm.id = defaultImage.length ? defaultImage[0].id : '';
- // 获取镜像列表
- this.noteBookImages = await getImageNameList({
- imageTypes: IMAGE_TYPE_ENUM.NOTEBOOK,
- imageResource: Number(IMAGE_RESOURCE_ENUM.PRESET),
- });
- if (this.noteBookForm.defaultImage) {
- this.noteBookTags = await getImageTagList({
- imageName: this.noteBookForm.defaultImage,
- imageTypes: IMAGE_TYPE_ENUM.NOTEBOOK,
- imageResource: Number(IMAGE_RESOURCE_ENUM.PRESET),
- });
- } else {
- this.noteBookTags = [];
- }
-
- this.notebookFormVisible = true;
- this.$nextTick(() => {
- this.clearValidate();
- });
- },
- clearValidate(...args) {
- this.$refs.noteBookFormRef.clearValidate.apply(this, args);
- },
- validateField(field) {
- this.$refs.noteBookFormRef.validateField(field);
- },
- onSubmitNotebookForm() {
- this.$refs.noteBookFormRef.validate((valid) => {
- if (valid) {
- this.notebookFormSubmitting = true;
- setDefaultImage({ id: this.noteBookForm.id })
- .then(() => {
- this.notebookFormVisible = false;
- this.crud.toQuery();
- })
- .finally(() => {
- this.notebookFormSubmitting = false;
- });
- }
- });
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .list-head {
- display: flex;
- justify-content: space-between;
- }
-
- .el-radio.is-bordered {
- width: 130px;
- height: 35px;
- padding: 10px 0;
- text-align: center;
- }
- </style>
|