Browse Source

model manage

tags/v1.22.11.2^2
chenshihai 3 years ago
parent
commit
403331e721
12 changed files with 1303 additions and 5 deletions
  1. +12
    -0
      templates/repo/modelmanage/create_local_1.tmpl
  2. +12
    -0
      templates/repo/modelmanage/create_local_2.tmpl
  3. +12
    -0
      templates/repo/modelmanage/create_online.tmpl
  4. +20
    -1
      templates/repo/modelmanage/index.tmpl
  5. +31
    -4
      web_src/js/components/Model.vue
  6. +48
    -0
      web_src/vuepages/apis/modules/fileupload.js
  7. +23
    -0
      web_src/vuepages/apis/modules/modelmanage.js
  8. +425
    -0
      web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-1.vue
  9. +677
    -0
      web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-2.vue
  10. +17
    -0
      web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-1.js
  11. +17
    -0
      web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-2.js
  12. +9
    -0
      web_src/vuepages/utils/index.js

+ 12
- 0
templates/repo/modelmanage/create_local_1.tmpl View File

@@ -0,0 +1,12 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelmanage-local-create-1.css?v={{MD5 AppVer}}" />
<style></style>
{{$repository := .Repository.ID}}
<div class="repository release dataset-list view">
{{template "repo/header" .}}
<div class="ui container">
<div id="__vue-root"></div>
</div>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-modelmanage-local-create-1.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 12
- 0
templates/repo/modelmanage/create_local_2.tmpl View File

@@ -0,0 +1,12 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelmanage-local-create-2.css?v={{MD5 AppVer}}" />
<style></style>
{{$repository := .Repository.ID}}
<div class="repository release dataset-list view">
{{template "repo/header" .}}
<div class="ui container">
<div id="__vue-root"></div>
</div>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-modelmanage-local-create-2.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 12
- 0
templates/repo/modelmanage/create_online.tmpl View File

@@ -0,0 +1,12 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelmanage-local-create-1.css?v={{MD5 AppVer}}" />
<style></style>
{{$repository := .Repository.ID}}
<div class="repository release dataset-list view">
{{template "repo/header" .}}
<div class="ui container">
<div id="__vue-root"></div>
</div>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-modelmanage-local-create-1.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 20
- 1
templates/repo/modelmanage/index.tmpl View File

@@ -25,6 +25,23 @@
border-bottom-left-radius: 4px;
box-shadow: 0 2px 3px 0 rgb(34 36 38 / 15%);
}
.m-blue-btn {
background-color: rgb(22, 132, 252) !important;
}
.m-blue-btn:hover {
background-color: #66b1ff !important;
color: #fff;
}

.m-blue-btn:focus {
background-color: #66b1ff !important;
color: #fff;
}

.m-blue-btn:active {
background-color: #3a8ee6 !important;
color: #fff;
}
</style>
<link rel="stylesheet" href="/self/ztree/css/zTreeStyle/zTreeStyle.css" type="text/css">

@@ -57,8 +74,10 @@
</div>
<div class="column right aligned">
<!-- -->
<a class="ui button {{if .Permission.CanWrite $.UnitTypeModelManage}} blue m-blue-btn {{else}} disabled {{end}}"
href="{{.RepoLink}}/modelmanage/create_local_model_tmpl1">导入本地模型</a>
<a class="ui button {{if .Permission.CanWrite $.UnitTypeModelManage}} green {{else}} disabled {{end}}"
onclick="showcreate(this)">{{$.i18n.Tr "repo.model.manage.import_new_model"}}</a>
onclick="showcreate(this)">导入线上模型</a>
</div>
</div>
{{if eq $.MODEL_COUNT 0}}


+ 31
- 4
web_src/js/components/Model.vue View File

@@ -23,6 +23,7 @@
<i class="el-icon-arrow-right"></i>
</div>
<!-- <i class="el-icon-time"></i> -->
<span :class="scope.row.modelType == '1' ? 'm-local' : 'm-online'">{{ scope.row.modelType == '1' ? '本地' : '线上' }}</span>
<a
class="text-over"
:href="showinfoHref + scope.row.name"
@@ -136,7 +137,7 @@
>
<template slot-scope="scope">
<div class="space-around">
<a
<!--<a
:style="{
visibility: !scope.row.Children ? 'visible' : 'hidden',
}"
@@ -149,6 +150,12 @@
)
"
>{{ i18n.model_create_new_ver }}</a
>-->
<a
v-if="scope.row.modelType == 1"
:href="url + 'create_local_model_tmpl1?type=1&name=' + scope.row.name + '&id=' + scope.row.id"
:class="{ disabled: !scope.row.isCanOper }"
>{{ '修改' }}</a
>
<a
:href="loadhref + scope.row.id"
@@ -223,9 +230,10 @@ export default {
let tableData;
tableData = res.data;
for (let i = 0; i < tableData.length; i++) {
trainTaskInfo = JSON.parse(tableData[i].trainTaskInfo);
trainTaskInfo = JSON.parse(tableData[i].trainTaskInfo || '{}');
tableData[i].engineName = this.getEngineName(tableData[i]);
tableData[i].computeResource = trainTaskInfo.ComputeResource;
tableData[i].computeResource = tableData[i].type == '0' ? 'CPU/GPU' : 'NPU';
tableData[i].cName = tableData[i].name;
tableData[i].rowKey = tableData[i].id + Math.random();
tableData[i].name = "";
@@ -483,13 +491,14 @@ export default {
let trainTaskInfo;
this.tableData = res.data.data;
for (let i = 0; i < this.tableData.length; i++) {
trainTaskInfo = JSON.parse(this.tableData[i].trainTaskInfo);
trainTaskInfo = JSON.parse(this.tableData[i].trainTaskInfo || '{}');
this.tableData[i].cName = this.tableData[i].name;
this.tableData[i].rowKey = this.tableData[i].id + Math.random();
this.tableData[i].engineName = this.getEngineName(
this.tableData[i]
);
this.tableData[i].computeResource = trainTaskInfo.ComputeResource;
// this.tableData[i].computeResource = trainTaskInfo.ComputeResource;
this.tableData[i].computeResource = this.tableData[i].type == '0' ? 'CPU/GPU' : 'NPU';
this.tableData[i].hasChildren =
res.data.data[i].versionCount === 1 ? false : true;
if (this.tableData[i].status !== 1) {
@@ -615,6 +624,24 @@ export default {
white-space: nowrap;
}

.m-local {
background-color: rgb(22, 132, 252);
color: white;
padding: 2px 3px;
border-radius: 4px;
font-size: 12px;
margin-right: 2px;
}

.m-online {
background-color: rgb(91, 185, 115);
color: white;
padding: 2px 3px;
border-radius: 4px;
font-size: 12px;
margin-right: 2px;
}

.el-icon-arrow-right {
font-family: element-icons !important;
speak: none;


+ 48
- 0
web_src/vuepages/apis/modules/fileupload.js View File

@@ -0,0 +1,48 @@
import service from "../service";

// 上传文件1: 获取文件chunks信息
// params: { md5, type: 0-CPU/GPU,1-NPU, file_name, scene: ''-数据集,'model'-模型 }
// return: uploadID, uuid, uploaded, chunks, attachID, datasetID(), datasetName(), fileName
export const getChunks = (params) => {
return service({
url: `/attachments/get_chunks`,
method: 'get',
params,
data: {},
});
};

// 上传文件2: 上传新文件
// params: { totalChunkCounts, md5, size, fileType, type, file_name, scene=model, modeluuid=xxxx }
// return: uploadID, uuid
export const getNewMultipart = (params) => {
return service({
url: `/attachments/new_multipart`,
method: 'get',
params,
data: {},
});
};

// 上传文件3: 获取分片上传地址
// params: { uuid, uploadID, size, chunkNumber, type, file_name, scene=model }
// return: url
export const getMultipartUrl = (params) => {
return service({
url: `/attachments/get_multipart_url`,
method: 'get',
params,
data: {},
});
};

// 上传文件4: 完成上传后
// data: { uuid, uploadID, size, type, file_name, dataset_id, description, scene=model, modeluuid=xxxx }
export const setCompleteMultipart = (data) => {
return service({
url: `/attachments/complete_multipart`,
method: 'post',
params: {},
data,
});
};

+ 23
- 0
web_src/vuepages/apis/modules/modelmanage.js View File

@@ -0,0 +1,23 @@
import service from "../service";
import Qs from 'qs';

// 保存本地模型
export const saveLocalModel = (data) => {
return service({
url: `${data.repo}/modelmanage/create_local_model`,
method: 'post',
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
params: {},
data: Qs.stringify(data),
});
};

// 求模型信息
export const getModelInfoByName = (params) => {
return service({
url: `${params.repo}/modelmanage/show_model_info_api`,
method: 'get',
params,
data: {},
});
};

+ 425
- 0
web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-1.vue View File

@@ -0,0 +1,425 @@
<template>
<div>
<div class="header">
<span class="title">{{ type == '1' ? '修改模型信息' : '导入本地模型' }}</span>
</div>
<div class="content">
<div class="guide-c" v-if="type != '1'">
<div class="step focused">
<div class="num">1</div>
<div class="txt">创建模型</div>
</div>
<div class="line"></div>
<div class="step">
<div class="num">2</div>
<div class="txt">上传模型文件</div>
</div>
</div>
<div class="row-c" v-loading="loading">
<div class="row">
<div class="r-title"><label class="required">可用集群</label></div>
<div class="r-content" style="display:flex;">
<div class="cluster-type-btn" v-if="type != 1 || type == 1 && state.type === 0"
:class="state.type === 0 ? 'focused' : ''"
:style="type != 1 ? 'border-top-right-radius:unset;border-bottom-right-radius:unset;' : ''"
@click="state.type = 0">
<svg xmlns="http://www.w3.org/2000/svg"
class="icon styles__StyledSVGIconPathComponent-sc-16fsqc8-0 cSaWDI svg-icon-path-icon fill"
viewBox="0 0 32 32" width="16" height="16">
<defs data-reactroot=""></defs>
<g>
<path
d="M4 3.989c0-0.731 0.593-1.323 1.324-1.323h21.352c0.729 0.005 1.318 0.594 1.324 1.322v24.022c-0.001 0.731-0.593 1.323-1.324 1.323h-21.352c-0.729-0.005-1.318-0.594-1.324-1.322v-24.022zM25.333 14.667v-9.333h-18.667v9.333h18.667zM25.333 17.333h-18.667v9.333h18.667v-9.333zM12 8h8v2.667h-8v-2.667zM12 20h8v2.667h-8v-2.667z">
</path>
</g>
</svg>
<span class="txt">CPU / GPU</span>
</div>
<div class="cluster-type-btn" v-if="type != 1 || type == 1 && state.type === 1"
:class="state.type === 1 ? 'focused' : ''"
:style="type != 1 ? 'border-top-left-radius:unset;border-bottom-left-radius:unset;' : ''"
@click="state.type = 1">
<svg xmlns="http://www.w3.org/2000/svg"
class="icon styles__StyledSVGIconPathComponent-sc-16fsqc8-0 iKfgJk svg-icon-path-icon fill"
viewBox="0 0 32 32" width="16" height="16">
<defs data-reactroot=""></defs>
<g>
<path
d="M4 3.989c0-0.731 0.593-1.323 1.324-1.323h21.352c0.729 0.005 1.318 0.594 1.324 1.322v24.022c-0.001 0.731-0.593 1.323-1.324 1.323h-21.352c-0.729-0.005-1.318-0.594-1.324-1.322v-24.022zM25.333 14.667v-9.333h-18.667v9.333h18.667zM25.333 17.333h-18.667v9.333h18.667v-9.333zM12 8h8v2.667h-8v-2.667zM12 20h8v2.667h-8v-2.667z">
</path>
</g>
</svg>
<span class="txt">Ascend NPU</span>
</div>
</div>
</div>
<div class="row">
<div class="r-title"><label class="required">模型名称</label></div>
<div class="r-content">
<el-input size="medium" :maxLength="25" v-model="state.name" placeholder="请输入模型名称"></el-input>
</div>
</div>
<div class="row">
<div class="r-title"><label class="required">版本</label></div>
<div class="r-content">
<el-input class="input-disabled" style="width:288px;" size="medium" v-model="state.version" readonly>
</el-input>
</div>
</div>
<div class="row">
<div class="r-title"><label class="required">模型框架</label></div>
<div class="r-content">
<el-select style="width:288px;" size="medium" v-model="state.engine" placeholder="请选择">
<el-option v-for="item in engineList" :key="item.k" :label="item.v" :value="item.k">
</el-option>
</el-select>
</div>
</div>
<div class="row">
<div class="r-title"><label>模型标签</label></div>
<div class="r-content">
<el-input size="medium" :maxLength="255" v-model="state.label" placeholder="输入标签,多个标签用空格区分"></el-input>
</div>
</div>
<div class="row" style="align-items:flex-start;">
<div class="r-title"><label>模型描述</label></div>
<div class="r-content">
<el-input type="textarea" :maxLength="255" size="medium" v-model="state.description" :rows="3"
placeholder="描述字数不超过255个字符">
</el-input>
</div>
</div>
<div class="row" style="margin-top:20px">
<div class="r-title"><label></label></div>
<div class="r-content">
<el-button size="medium" class="green" @click="submit">保存模型</el-button>
<el-button size="medium" @click="cancel">取消</el-button>
</div>
</div>
</div>
</div>
</div>
</template>

<script>

import { saveLocalModel, getModelInfoByName } from '~/apis/modules/modelmanage';
// import { CLUSTERS, COMPUTER_RESOURCES, ACC_CARD_TYPE } from '~/const';
import { getUrlSearchParams } from '~/utils';
// import { formatDate } from 'element-ui/lib/utils/date-util';

const REPO_NAME = location.pathname.split('/')[2];

export default {
data() {
return {
type: '0', // 1-修改,其它-新增
loading: false,
state: {
type: 0,
name: REPO_NAME + '_model_' + Math.random().toString(36).substr(2, 4),
version: '0.0.1',
engine: '0',
label: '',
description: '',
},
engineList: [{ k: '0', v: 'PyTorch' }, { k: '1', v: 'TensorFlow' }, { k: '2', v: 'MindSpore' }, { k: '4', v: 'PaddlePaddle' }, { k: '5', v: 'OneFlow' }, { k: '6', v: 'MXNet' }, { k: '3', v: 'Other' }],
};
},
components: { /*QueueDialog*/ },
methods: {
submit() {
console.log(this.state);
if (!this.state.name) {
this.$message({
type: 'info',
message: '请输入模型名称',
});
return;
}
saveLocalModel({
repo: location.pathname.split('/').slice(0, 3).join('/'),
...this.state,
}).then(res => {
res = res.data;
if (res && res.code == '0') {
if (this.type == '1') {
this.cancel();
return;
}
const list = window.location.href.split('/');
list.pop();
list.push('create_local_model_tmpl2');
window.location.href = list.join('/') + '?type=0name=' + this.state.name + '&id=' + res.id;
} else {
this.$message({
type: 'error',
message: '模型保存失败',
});
}
}).catch(err => {
console.log(err);
});
},
cancel() {
const list = window.location.href.split('/');
list.pop();
list.push('show_model');
window.location.href = list.join('/');
},
},
mounted() {
const urlParams = getUrlSearchParams();
if (urlParams.type == '1' && urlParams.name && urlParams.id) { // update
this.type = urlParams.type;
this.state.name = urlParams.name;
this.state.id = urlParams.id;
this.loading = true;
getModelInfoByName({
repo: location.pathname.split('/').slice(0, 3).join('/'),
name: urlParams.name,
id: urlParams.id
}).then(res => {
this.loading = false;
const list = res.data;
if (list && list.length) {
const data = list[0];
this.state.type = data.type;
this.state.id = data.id;
this.state.name = data.name;
this.state.version = data.version;
this.state.engine = data.engine.toString();
this.state.label = data.label;
this.state.description = data.description;
}
}).catch(err => {
this.loading = false;
console.log(err);
this.cancel();
});
}
},
beforeDestroy() {
},
};
</script>

<style scoped lang="less">
.header {
height: 45px;
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
border-radius: 5px 5px 0px 0px;
font-size: 14px;
background: rgb(240, 240, 240);
display: flex;
align-items: center;

.title {
font-weight: 600;
font-size: 16px;
color: rgb(16, 16, 16);
margin-left: 10px;
}
}

.content {
margin-top: -1px;
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
padding: 30px 0;
border-top: none;

.guide-c {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;

.step {
display: flex;
align-items: center;
color: rgb(136, 136, 136);

.num {
border: 1px solid rgb(136, 136, 136);
width: 34px;
height: 34px;
font-size: 24px;
text-align: center;
line-height: 34px;
border-radius: 17px;
margin-right: 8px;
}

.txt {
font-weight: 600;
}

&.focused {
color: rgb(22, 132, 252);

.num {
border-color: rgb(22, 132, 252);
}
}
}

.line {
height: 2px;
width: 50px;
background-color: rgb(187, 187, 187);
margin: 0 10px;
}
}

.row-c {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 0 auto;
width: 80%;

.row {
width: 100%;
display: flex;
align-items: center;
margin: 8px 0;
margin-left: -190px;

.r-title {
text-align: right;
font-size: .92857143em;
font-weight: 700;
color: rgba(0, 0, 0, .87);
width: 200px;
margin-right: 28px;
position: relative;

.required {
&::after {
position: absolute;
margin: -0.2em 0 0 0.2em;
content: '*';
color: #db2828;
}
}
}

.r-content {
flex: 1;

.cluster-type-btn {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
height: 36px;
padding: 10px;
cursor: pointer;
border-radius: 4px;

.icon {
margin-right: 5px;
}

.txt {}

&.focused {
border-color: rgb(50, 145, 248);
color: rgb(50, 145, 248);
cursor: default;

.icon {
:not([stroke]) {
fill: rgb(50, 145, 248);
}
}
}
}
}
}
}
}

.input-disabled {
/deep/ .el-input__inner {
background-color: #f5f5f6 !important;
color: #888888 !important;
}
}

.el-select-dropdown__item.selected {
color: rgba(0, 0, 0, .95);
}

/deep/ .el-button {
background-color: #e0e1e2;
color: rgba(0, 0, 0, .6);
border-color: transparent;
transition: opacity .1s ease, background-color .1s ease, color .1s ease, box-shadow .1s ease, background .1s ease, -webkit-box-shadow .1s ease;
will-change: auto;
-webkit-tap-highlight-color: transparent;

&:hover {
border-color: transparent;
background-color: #cacbcd;
color: rgba(0, 0, 0, .8);
}

&:focus {
background-color: #cacbcd;
color: rgba(0, 0, 0, .8);
border-color: transparent;
}

&:active {
background-color: #babbbc;
color: rgba(0, 0, 0, .9);
border-color: transparent;
}

&.green {
background-color: #5bb973;
color: #fff;

&:hover {
background-color: #16ab39;
border-color: transparent;
}

&:focus {
background-color: #0ea432;
border-color: transparent;
}

&:active {
background-color: #198f35;
border-color: transparent;
}
}
}

/deep/ .el-select {
.is-focus {
.el-input__inner {
border-color: #85b7d9;
}
}
}

/deep/ .el-input__inner {
&:focus {
border-color: #85b7d9;
}
}

/deep/ .el-textarea__inner {
&:focus {
border-color: #85b7d9;
}
}
</style>

+ 677
- 0
web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-2.vue View File

@@ -0,0 +1,677 @@
<template>
<div>
<div class="header">
<span class="title">{{ type == '1' ? '增加模型文件' : '上传模型文件' }}</span>
</div>
<div class="content">
<div class="guide-c" v-if="type != '1'">
<div class="step">
<div class="num">1</div>
<div class="txt">创建模型</div>
</div>
<div class="line"></div>
<div class="step focused">
<div class="num">2</div>
<div class="txt">上传模型文件</div>
</div>
</div>
<div class="row-c">
<div class="row">
<div class="r-title"><label class="required">模型名称</label></div>
<div class="r-content">
<el-input size="medium" class="input-disabled" v-model="state.name" placeholder="请输入模型名称" readonly>
</el-input>
</div>
</div>
<div class="row" style="align-items:flex-start;">
<div class="r-title"><label class="required">文件上传</label></div>
<div class="r-content">
<div>
<form class="dropzone" ref="dropzoneRef"></form>
</div>
</div>
</div>
<div class="row" style="margin-top:10px">
<div class="r-title"><label></label></div>
<div class="r-content">
<el-button size="medium" class="green" @click="submit">上传</el-button>
<el-button size="medium" @click="cancel">取消</el-button>
</div>
</div>
<div class="row" style="align-items:flex-start;">
<div class="r-title"><label>上传状态:</label></div>
<div class="r-content">

</div>
</div>
</div>
</div>
</div>
</template>

<script>

import { getModelInfoByName } from '~/apis/modules/modelmanage';
import { getChunks, getNewMultipart, getMultipartUrl, setCompleteMultipart } from '~/apis/modules/fileupload';
// import { CLUSTERS, COMPUTER_RESOURCES, ACC_CARD_TYPE } from '~/const';
import { getUrlSearchParams } from '~/utils';
// import { formatDate } from 'element-ui/lib/utils/date-util';

import 'dropzone/dist/dropzone.css';
import Dropzone from 'dropzone';
import SparkMD5 from "spark-md5";

Dropzone.autoDiscover = false;

const uploadChunkSize = 1024 * 1024 * 64;
const md5ChunkSize = 1024 * 1024 * 64;

export default {
data() {
return {
dropzoneHandler: null,
type: '0', // 1-修改,其它-新增
state: {
type: '',
id: '',
name: '',
version: '',
engine: '',
label: '',
description: '',
size: '',
}
};
},
components: { /*QueueDialog*/ },
methods: {
initDropZone() {
this.dropzoneHandler = new Dropzone(this.$refs.dropzoneRef, {
url: '/',
parallelUploads: 20,
uploadMultiple: true,
maxFilesize: 2048,
timeout: 0,
addRemoveLinks: true,
autoProcessQueue: false,
dictDefaultMessage: '点击添加文件或直接拖拽文件到此处',
dictInvalidFileType: 'dictInvalidFileType',
dictFileTooBig: 'dictFileTooBig',
dictRemoveFile: 'dictRemoveFile',
});
this.dropzoneHandler.on("addedfile", file => {
console.log(`File added: ${file.name}`);
console.log(file);
// this.calcFileMd5(file);
});
this.dropzoneHandler.on("removedfile", file => {
console.log(`File removedfile: ${file.name}`);
});
this.dropzoneHandler.on("maxfilesexceeded", file => {
console.log(`File removedfile: ${file.name}`);
});
},
calcFileMd5(file) {
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const chunkSize = md5ChunkSize;
const chunks = Math.ceil(file.size / chunkSize);
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
file.totalChunkCounts = chunks;
if (file.size == 0) {
file.totalChunkCounts = 1;
}
let currentChunk = 0;
return new Promise((resolve, reject) => {
fileReader.onload = function (e) {
console.log(file.name + ': read chunk nr', currentChunk + 1, 'of', chunks);
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
console.log(file.name + ': finished loading');
console.info(file.name + ': computed hash', spark.end());
const md5 = spark.end();
spark.destroy();
file.uniqueIdentifier = md5;
resolve(md5);
}
};
fileReader.onerror = function (e) {
console.warn(file.name + ': oops, calcFileMd5 went wrong.');
reject(e);
};

function loadNext() {
const start = currentChunk * chunkSize;
const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
},
async _getChunksInfo(file) {
const params = {
params: {
md5: file.uniqueIdentifier,
type: this.state.type,
file_name: file.name,
_csrf: csrf,
},
};
try {
// const response = axios.get("/attachments/get_chunks", params);
const response = await getChunks(params);
file.uploadID = response.data.uploadID;
file.uuid = response.data.uuid;
file.uploaded = response.data.uploaded;
file.chunks = response.data.chunks;
file.attachID = response.data.attachID;
file.datasetID = response.data.datasetID;
file.datasetName = response.data.datasetName;
file.realName = response.data.fileName;
return file;
} catch (error) {
this.emitDropzoneFailed(file);
console.log("getChunksInfo catch: ", error);
return null;
}
},
getChunksInfo(file) {
return getChunks({
md5: file.uniqueIdentifier,
type: this.state.type,
file_name: file.name,
scene: 'model',
}).then(res => {
const data = res.data;
file.uploadID = data.uploadID;
file.uuid = data.uuid;
file.uploaded = data.uploaded;
file.chunks = data.chunks;
file.attachID = data.attachID;
// file.datasetID = data.datasetID;
// file.datasetName = data.datasetName;
file.modelUuid = data.modeluuid;
file.modelName = data.modelName;
file.realName = data.fileName;
return file;
}).catch(err => {
console.info('getChunksInfo', err);
return err;
});
},
async _newUpload(file) {
const res = await axios.get("/attachments/new_multipart", {
params: {
totalChunkCounts: file.totalChunkCounts,
md5: file.uniqueIdentifier,
size: file.size,
fileType: file.type,
type: this.uploadtype,
file_name: file.name,
_csrf: csrf,
},
});
file.uploadID = res.data.uploadID;
file.uuid = res.data.uuid;
},
newUpload(file) {
return getNewMultipart({
totalChunkCounts: file.totalChunkCounts,
md5: file.uniqueIdentifier,
size: file.size,
fileType: file.type,
type: this.state.type,
file_name: file.name,
scene: 'model',
modeluuid: file.modelUuid,
}).then(res => {
const data = res.data;
file.uploadID = data.uploadID;
file.uuid = data.uuid;
if (file.uploadID && file.uuid) {
file.chunks = '';
this.breakpointUpload(file);
}
return file;
}).catch(err => {
console.log('getNewMultipart', err);
return err;
});
},
breakpointUpload(file) {
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const chunks = Math.ceil(file.size / chunkSize);
const fileReader = new FileReader();
const time = new Date().getTime();
const chunkSize = uploadChunkSize;
const _this = this;
let currentChunk = 0;
const successChunks = [];
const successParts = file.chunks.split(",");
for (let i = 0; i < successParts.length; i++) {
successChunks[i] = successParts[i].split("-")[0];
}
const urls = [];
const etags = [];
function checkSuccessChunks() {
const index = successChunks.indexOf((currentChunk + 1).toString());
if (index == -1) {
return false;
}
return true;
}

async function getUploadChunkUrl(currentChunk, partSize) {
const res = await getMultipartUrl({
uuid: file.uuid,
uploadID: file.uploadID,
size: partSize,
chunkNumber: currentChunk + 1,
type: _this.state.type,
file_name: file.name,
scene: 'model',
});
urls[currentChunk] = res.data.url;
}

async function uploadMinioNewMethod(url, e) {
const xhr = new XMLHttpRequest();
xhr.open("PUT", url, false);
if (_this.state.type == 0) {
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(e.target.result);
const etagValue = xhr.getResponseHeader("etag");
etags[currentChunk] = etagValue;
} else if (_this.state.type == 1) {
xhr.setRequestHeader("Content-Type", "");
xhr.send(e.target.result);
const etagValue = xhr.getResponseHeader("ETag");
etags[currentChunk] = etagValue;
}
}

async function uploadChunk(e) {
try {
if (!checkSuccessChunks()) {
const start = currentChunk * chunkSize;
const partSize = start + chunkSize >= file.size ? file.size - start : chunkSize;
// 获取分片上传url
await getUploadChunkUrl(currentChunk, partSize);
if (urls[currentChunk] != '') {
await uploadMinioNewMethod(urls[currentChunk], e);
if (etags[currentChunk] != '') {
} else {
console.log("上传到minio uploadChunk etags[currentChunk] == ''"); // TODO
}
} else {
console.log("uploadChunk urls[currentChunk] != ''"); // TODO
}
}
} catch (error) {
console.log(error);
}
}

async function completeUpload() {
return await setCompleteMultipart({
uuid: file.uuid,
uploadID: file.uploadID,
file_name: file.name,
size: file.size,
type: _this.state.type,
scene: 'model',
modeluuid: file.modelUuid,
});
}

function loadNext() {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}

fileReader.onload = async (e) => {
try {
await uploadChunk(e);
fileReader.abort();
currentChunk++;
if (currentChunk < chunks) {
console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk + 1}/${chunks}个分片上传`);
// this.progress = Math.ceil((currentChunk / chunks) * 100);
// this.updateProgress(file, this.dropzoneParams.data("uploading"), Number(((currentChunk / chunks) * 100).toFixed(2)), 3);
// this.status = `${this.dropzoneParams.data("uploading")} ${((currentChunk / chunks) * 100).toFixed(2)}%`;
loadNext();
} else {
try {
await completeUpload();
console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time) / 1000} s`);
// this.uploadLength++;
// this.allUploadLength++;
// this.updateProgress(file, "上传成功", 100, 0);
// this.finishUpload(file);
} catch (err) {
const info = "上传失败";
console.log(info, file)
// this.allUploadLength++;
// this.uploadError(file, info);
// this.updateProgress(file, info, Number(((currentChunk / chunks) * 100).toFixed(2)) - 1, 2);
}
}
} catch (err) {
console.log(err);
}
};
console.log("上传分片...");
loadNext();
},
submit() {
console.log(this.dropzoneHandler);
const fileList = this.dropzoneHandler.getAcceptedFiles();
if (!fileList.length) return;
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
const file = fileList[i];
this.calcFileMd5(file).then(res => { // 计算MD5
this.getChunksInfo(file).then(res => { // 获取Chunk信息
if (file.uploadID == '' || file.uuid == '') { // 未上传过
this.newUpload(file);
} else if (file.uploaded == '1') { // 已上传成功 秒传
if (file.attachID == '0') { // 删除数据集记录,未删除文件
// await addAttachment(file);
console.log(`file.attachID == '0'`);
}
// 同一模型上传同一个文件
if (file.modelUuid != '') {
if (file.modelName != '' && file.realName != '') {
let info = `该文件已上传在模型: ${file.modelName}`;
console.log(info);
// this.uploadError(file, info);
// this.allUploadLength++;
// this.updateProgress(file, "上传失败", 0, 1, info);
}
}
console.log('文件上传处理完成');
} else { // 断点续传
this.breakpointUpload(file);
}
}).catch(err => {
console.info('getChunksInfo', err);
});
}).catch(err => {
console.info('calcFileMd5', err);
});
}
},
cancel() {
const list = window.location.href.split('/');
list.pop();
list.push('show_model');
window.location.href = list.join('/');
},
},
mounted() {
const urlParams = getUrlSearchParams();
if (urlParams.name && urlParams.id) {
this.type = urlParams.type || '0';
this.state.name = urlParams.name;
this.state.id = urlParams.id;
this.loading = true;
getModelInfoByName({
repo: location.pathname.split('/').slice(0, 3).join('/'),
name: urlParams.name,
id: urlParams.id
}).then(res => {
this.loading = false;
const list = res.data;
if (list && list.length) {
const data = list[0];
this.state.type = data.type;
this.state.id = data.id;
this.state.name = data.name;
this.state.version = data.version;
this.state.engine = data.engine.toString();
this.state.label = data.label;
this.state.description = data.description;
this.state.size = data.size;
}
}).catch(err => {
this.loading = false;
console.log(err);
this.cancel();
});
} else {
this.cancel();
return;
}
this.initDropZone();
},
beforeDestroy() {
},
};
</script>

<style scoped lang="less">
.header {
height: 45px;
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
border-radius: 5px 5px 0px 0px;
font-size: 14px;
background: rgb(240, 240, 240);
display: flex;
align-items: center;

.title {
font-weight: 600;
font-size: 16px;
color: rgb(16, 16, 16);
margin-left: 10px;
}
}

.content {
margin-top: -1px;
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
padding: 30px 0;
border-top: none;

.guide-c {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;

.step {
display: flex;
align-items: center;
color: rgb(136, 136, 136);

.num {
border: 1px solid rgb(136, 136, 136);
width: 34px;
height: 34px;
font-size: 24px;
text-align: center;
line-height: 34px;
border-radius: 17px;
margin-right: 8px;
}

.txt {
font-weight: 600;
}

&.focused {
color: rgb(22, 132, 252);

.num {
border-color: rgb(22, 132, 252);
}
}
}

.line {
height: 2px;
width: 50px;
background-color: rgb(187, 187, 187);
margin: 0 10px;
}
}

.row-c {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 0 auto;
width: 80%;

.row {
width: 100%;
display: flex;
align-items: center;
margin: 8px 0;
margin-left: -190px;

.r-title {
text-align: right;
font-size: .92857143em;
font-weight: 700;
color: rgba(0, 0, 0, .87);
width: 200px;
margin-right: 28px;
position: relative;

.required {
&::after {
position: absolute;
margin: -0.2em 0 0 0.2em;
content: '*';
color: #db2828;
}
}
}

.r-content {
flex: 1;

.cluster-type-btn {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
height: 36px;
padding: 10px;
cursor: pointer;
border-radius: 4px;

.icon {
margin-right: 5px;
}

.txt {}

&.focused {
border-color: rgb(50, 145, 248);
color: rgb(50, 145, 248);
cursor: default;

.icon {
:not([stroke]) {
fill: rgb(50, 145, 248);
}
}
}
}
}
}
}
}

.input-disabled {
/deep/ .el-input__inner {
background-color: #f5f5f6 !important;
color: #888888 !important;
}
}

/deep/ .dz-progress {
display: none;
}

.el-select-dropdown__item.selected {
color: #85b7d9;
}

/deep/ .el-button {
background-color: #e0e1e2;
color: rgba(0, 0, 0, .6);
border-color: transparent;
transition: opacity .1s ease, background-color .1s ease, color .1s ease, box-shadow .1s ease, background .1s ease, -webkit-box-shadow .1s ease;
will-change: auto;
-webkit-tap-highlight-color: transparent;

&:hover {
border-color: transparent;
background-color: #cacbcd;
color: rgba(0, 0, 0, .8);
}

&:focus {
background-color: #cacbcd;
color: rgba(0, 0, 0, .8);
border-color: transparent;
}

&:active {
background-color: #babbbc;
color: rgba(0, 0, 0, .9);
border-color: transparent;
}

&.green {
background-color: #5bb973;
color: #fff;

&:hover {
background-color: #16ab39;
border-color: transparent;
}

&:focus {
background-color: #0ea432;
border-color: transparent;
}

&:active {
background-color: #198f35;
border-color: transparent;
}
}
}

/deep/ .el-select {
.is-focus {
.el-input__inner {
border-color: #85b7d9;
}
}
}

/deep/ .el-input__inner {
&:focus {
border-color: #85b7d9;
}
}

/deep/ .el-textarea__inner {
&:focus {
border-color: #85b7d9;
}
}
</style>

+ 17
- 0
web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-1.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './modelmanage-local-create-1.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 17
- 0
web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-2.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './modelmanage-local-create-2.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 9
- 0
web_src/vuepages/utils/index.js View File

@@ -5,3 +5,12 @@ export const getListValueWithKey = (list, key, k = 'k', v = 'v') => {
}
return '';
};

export const getUrlSearchParams = () => {
const params = new URLSearchParams(location.search);
const obj = {};
params.forEach((value, key) => {
obj[key] = value;
});
return obj;
};

Loading…
Cancel
Save