Browse Source

Resources Management

pull/2765/head
chenshihai 2 years ago
parent
commit
515d2df74a
26 changed files with 20752 additions and 20 deletions
  1. +19351
    -17
      package-lock.json
  2. +1
    -0
      package.json
  3. +9
    -0
      routers/admin/resources.go
  4. +3
    -3
      templates/admin/navbar.tmpl
  5. +10
    -0
      templates/admin/resources/queue.tmpl
  6. +10
    -0
      templates/admin/resources/scene.tmpl
  7. +10
    -0
      templates/admin/resources/specification.tmpl
  8. +41
    -0
      web_src/vuepages/apis/modules/point.js
  9. +26
    -0
      web_src/vuepages/apis/service.js
  10. +113
    -0
      web_src/vuepages/components/BaseDialog.vue
  11. +9
    -0
      web_src/vuepages/const/index.js
  12. +70
    -0
      web_src/vuepages/langs/config/en-US.js
  13. +70
    -0
      web_src/vuepages/langs/config/zh-CN.js
  14. +16
    -0
      web_src/vuepages/langs/index.js
  15. +196
    -0
      web_src/vuepages/pages/resources/components/QueueDialog.vue
  16. +194
    -0
      web_src/vuepages/pages/resources/queue/index.vue
  17. +17
    -0
      web_src/vuepages/pages/resources/queue/vp-resources-queue.js
  18. +43
    -0
      web_src/vuepages/pages/resources/scene/index.vue
  19. +17
    -0
      web_src/vuepages/pages/resources/scene/vp-resources-scene.js
  20. +43
    -0
      web_src/vuepages/pages/resources/specification/index.vue
  21. +17
    -0
      web_src/vuepages/pages/resources/specification/vp-resources-specification.js
  22. +148
    -0
      web_src/vuepages/pages/reward/point/utils.js
  23. +16
    -0
      web_src/vuepages/pages/reward/point/vp-point.js
  24. +308
    -0
      web_src/vuepages/pages/reward/point/vp-point.vue
  25. +7
    -0
      webpack.config.js
  26. +7
    -0
      webpack_pro.config.js

+ 19351
- 17
package-lock.json
File diff suppressed because it is too large
View File


+ 1
- 0
package.json View File

@@ -54,6 +54,7 @@
"vue": "2.6.11",
"vue-bar-graph": "1.2.0",
"vue-calendar-heatmap": "0.8.4",
"vue-i18n": "6.1.3",
"vue-loader": "15.9.2",
"vue-router": "3.3.4",
"vue-template-compiler": "2.6.11",


+ 9
- 0
routers/admin/resources.go View File

@@ -12,13 +12,22 @@ const (
)

func GetQueuePage(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminResources"] = true
ctx.Data["PageIsAdminResourcesQueue"] = true
ctx.HTML(200, tplResourceQueue)
}

func GetSpecificationPage(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminResources"] = true
ctx.Data["PageIsAdminResourcesSpecification"] = true
ctx.HTML(200, tplResourceSpecification)
}

func GetScenePage(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminResources"] = true
ctx.Data["PageIsAdminResourcesScene"] = true
ctx.HTML(200, tplResourceScene)
}

+ 3
- 3
templates/admin/navbar.tmpl View File

@@ -30,13 +30,13 @@
<a class="item item-first" href="javascript:void;">
{{.i18n.Tr "admin.resource_management"}}
</a>
<a class="item item-next" href="javascript:void;">
<a class="{{if .PageIsAdminResourcesQueue}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/queue">
{{.i18n.Tr "admin.resource_pool"}}
</a>
<a class="item item-next" href="javascript:void;">
<a class="{{if .PageIsAdminResourcesSpecification}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/specification">
{{.i18n.Tr "admin.resource_price"}}
</a>
<a class="item item-next" href="javascript:void;">
<a class="{{if .PageIsAdminResourcesScene}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/scene">
{{.i18n.Tr "admin.application_scenario"}}
</a>
<a class="item item-first" href="javascript:void;">


+ 10
- 0
templates/admin/resources/queue.tmpl View File

@@ -0,0 +1,10 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-queue.css?v={{MD5 AppVer}}" />
<div class="admin resource">
{{template "admin/navbar" .}}
<div class="ui container">
<div id="__vue-root"></div>
</duv>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-resources-queue.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 10
- 0
templates/admin/resources/scene.tmpl View File

@@ -0,0 +1,10 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-scene.css?v={{MD5 AppVer}}" />
<div class="admin resource">
{{template "admin/navbar" .}}
<div class="ui container">
<div id="__vue-root"></div>
</duv>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-resources-scene.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 10
- 0
templates/admin/resources/specification.tmpl View File

@@ -0,0 +1,10 @@
{{template "base/head" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-specification.css?v={{MD5 AppVer}}" />
<div class="admin resource">
{{template "admin/navbar" .}}
<div class="ui container">
<div id="__vue-root"></div>
</duv>
</div>
<script src="{{StaticUrlPrefix}}/js/vp-resources-specification.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 41
- 0
web_src/vuepages/apis/modules/point.js View File

@@ -0,0 +1,41 @@
import service from '../service';

// 算力积分概要
export const getPointAccount = () => {
return service({
url: '/reward/point/account',
method: 'get',
params: {},
});
}

// 算力积分获取、消耗明细
// operate-INCREASE 表示获取明细 DECREASE表示消耗明细, page-当前页, pageSize-每页条数
export const getPointList = (params) => {
return service({
url: '/reward/point/record/list',
method: 'get',
params,
});
}

// 管理员充值、扣减用户积分
// TargetUserId, OperateType-INCREASE,DECREASE, Amount, Remark, RewardType-POINT
export const setPointOperate = (data) => {
return service({
url: '/operation/reward/point/account/operate',
method: 'post',
data,
params: {}
});
}

// 算力积分页面
export const getPoint = () => {
return service({
url: '/reward/point',
method: 'get',
params: {},
data: {},
});
}

+ 26
- 0
web_src/vuepages/apis/service.js View File

@@ -0,0 +1,26 @@
import axios from 'axios';

const service = axios.create({
baseURL: '/',
timeout: 20000,
});

service.interceptors.request.use((config) => {
config.data && Object.assign(config.data, {
_csrf: window.config ? window.config.csrf : '',
});
config.params && Object.assign(config.params, {
_csrf: window.config ? window.config.csrf : '',
});
return config;
}, (error) => {
return Promise.reject(error);
});

service.interceptors.response.use((response) => {
return response;
}, (error) => {
return Promise.reject(error);
});

export default service;

+ 113
- 0
web_src/vuepages/components/BaseDialog.vue View File

@@ -0,0 +1,113 @@
<template>
<div class="base-dlg">
<el-dialog
:visible.sync="dialogShow"
:title="title"
:width="width"
:fullscreen="fullscreen"
:top="top"
:modal="modal"
:modal-append-to-body="modalAppendToBody"
:append-to-body="appendToBody"
:lock-scroll="lockScroll"
:custom-class="customClass"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
:show-close="showClose"
:center="center"
:destroy-on-close="destroyOnClose"
:before-close="beforeClose"
@open="open"
@opened="opened"
@close="close"
@closed="closed"
>
<template v-slot:title>
<slot name="title"></slot>
</template>
<template v-slot:default>
<slot name="default"></slot>
</template>
<template v-slot:footer>
<slot name="footer"></slot>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "BaseDialog",
props: {
visible: { type: Boolean, default: false },
title: { type: String, default: "" },
width: { type: String, default: "" },
fullscreen: { type: Boolean, default: false },
top: { type: String },
modal: { type: Boolean, default: true },
modalAppendToBody: { type: Boolean, default: true },
appendToBody: { type: Boolean, default: false },
lockScroll: { type: Boolean, default: false },
customClass: { type: String, default: "" },
closeOnClickModal: { type: Boolean, default: false },
closeOnPressEscape: { type: Boolean, default: true },
showClose: { type: Boolean, default: true },
beforeClose: { type: Function },
center: { type: Boolean, default: false },
destroyOnClose: { type: Boolean, default: false },
},
data: function () {
return {
dialogShow: false,
};
},
watch: {
visible: function (val) {
this.dialogShow = val;
},
},
methods: {
open: function () {
this.$emit("open");
},
opened: function () {
this.$emit("opened");
},
close: function () {
this.$emit("close");
},
closed: function () {
this.$emit("closed");
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped lang="less">
.base-dlg {
/deep/ .el-dialog__header {
text-align: left;
height: 45px;
background: rgb(240, 240, 240);
border-radius: 5px 5px 0px 0px;
border-bottom: 1px solid rgb(212, 212, 213);
padding: 0 15px;
display: flex;
align-items: center;
font-weight: 500;
font-size: 16px;
color: rgb(16, 16, 16);
.el-dialog__title {
font-weight: 500;
font-size: 16px;
color: rgb(16, 16, 16);
}
.el-dialog__headerbtn {
top: 15px;
right: 15px;
}
}
/deep/ .el-dialog__body {
padding: 15px 15px;
}
}
</style>

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

@@ -0,0 +1,9 @@
import { i18n } from '~/langs';

export const SOURCE_TYPE = [{ k: 'ACCOMPLISH_TASK', v: i18n.t('accomplishTask') }, { k: 'ADMIN_OPERATE', v: i18n.t('adminOperate') }, { k: 'RUN_CLOUDBRAIN_TASK', v: i18n.t('runCloudBrainTask') }];
export const CONSUME_STATUS = [{ k: 'OPERATING', v: i18n.t('operating') }, { k: 'SUCCEEDED', v: i18n.t('succeeded') }];
export const POINT_ACTIONS = [
{ k: 1, v: i18n.t('createPublicProject') }, { k: 6, v: i18n.t('dailyPutforwardTasks') }, { k: 7, v: i18n.t('dailyPR') }, { k: 10, v: i18n.t('comment') }, { k: 24, v: i18n.t('uploadDatasetFile') }, { k: 30, v: i18n.t('importNewModel') }, { k: 34, v: i18n.t('completeWechatCodeScanningVerification') },
{ k: 35, v: i18n.t('dailyRunCloudbrainTasks') }, { k: 36, v: i18n.t('datasetRecommendedByThePlatform') }, { k: 37, v: i18n.t('submitNewPublicImage') }, { k: 38, v: i18n.t('imageRecommendedByThePlatform') }, { k: 39, v: i18n.t('firstChangeofAvatar') }, { k: 40, v: i18n.t('dailyCommit') },
];
export const JOB_TYPE = [{ k: 'DEBUG', v: i18n.t('debugTask') }, { k: 'TRAIN', v: i18n.t('trainTask') }, { k: 'INFERENCE', v: i18n.t('inferenceTask') }, { k: 'BENCHMARK', v: i18n.t('benchmarkTask') }];

+ 70
- 0
web_src/vuepages/langs/config/en-US.js View File

@@ -0,0 +1,70 @@
const en = {
loading: 'Loading...',
noData: 'No Data',
date: 'Date',

accomplishTask: 'Accomplish Task',
adminOperate: 'Administrator Operation',
runCloudBrainTask: 'Run CloudBrain Task',
operating: 'Operating',
succeeded: 'Succeeded',
debugTask: 'Debug Task',
trainTask: 'Train Task',
inferenceTask: 'Inference Task',
benchmarkTask: 'Benchmark Task',
createPublicProject: 'Create Public Projects',
dailyPutforwardTasks: 'Daily Put Forward Tasks',
dailyPR: 'Daily PR',
comment: 'Comment',
uploadDatasetFile: 'Upload Dataset Files',
importNewModel: 'Import New Models',
completeWechatCodeScanningVerification: 'Complete Wechat Code Scanning Verification',
dailyRunCloudbrainTasks: 'Daily Run Cloudbrain Tasks',
datasetRecommendedByThePlatform: 'Dataset Recommended by the Platform',
submitNewPublicImage: 'Submit New Public Images',
imageRecommendedByThePlatform: 'Image Recommended by the Platform',
firstChangeofAvatar: 'First Change of Avatar',
dailyCommit: 'Daily Commit',
calcPointDetails: 'Calculation Points Details',
calcPointAcquisitionInstructions: 'Calculation Points Acquisition Instructions',
CurrAvailableCalcPoints: 'Currently Available Calculation Points',
totalGainCalcPoints: 'Total Gain of Calculation Points',
totalConsumeCalcPoints: 'Total Consume of Calculation Points',
gainDetail: 'Gain Detail',
consumeDetail: 'Consume Detail',
serialNumber: 'Serial Number',
time: 'Time',
scene: 'Scene',
behaviorOfPoint: 'Behavior Of Point',
explanation: 'Explanation',
points: 'Points',
status: 'Status',
runTime: 'Run Time',
taskName: 'Task Name',

createdRepository: 'created repository ',
openedIssue: 'opened issue ',
createdPullRequest: 'created pull request ',
commentedOnIssue: 'commented on issue ',
uploadDataset: 'upload dataset ',
createdNewModel: 'created new model ',
firstBindingWechatRewards: 'first binding wechat rewards',
created: 'created ',
type: ' type ',
dataset: 'dataset ',
setAsRecommendedDataset: ' was set as recommended dataset',
committedImage: 'committed image ',
image: 'image ',
setAsRecommendedImage: ' was set as recommended image',
updatedAvatar: 'updated avatar',
pushedBranch: 'pushed to {branch} at ',
dailyMaxTips: `can't get full points when reach the daily upper limit`,
memory: 'Memory',
sharedMemory: 'Shared Memory',
';': ', ',

noPointGainRecord: 'No Point Earn Record Yet',
noPointConsumeRecord: 'No Point Consume Record Yet',
}

export default en;

+ 70
- 0
web_src/vuepages/langs/config/zh-CN.js View File

@@ -0,0 +1,70 @@
const zh = {
loading: '加载中...',
noData: '暂无数据',
date: '日期',

accomplishTask: '积分任务',
adminOperate: '管理员操作',
runCloudBrainTask: '运行云脑任务',
operating: '消耗中',
succeeded: '已完成',
debugTask: '调试任务',
trainTask: '训练任务',
inferenceTask: '推理任务',
benchmarkTask: '评测任务',
createPublicProject: '创建公开项目',
dailyPutforwardTasks: '每日提出任务',
dailyPR: '每日提出PR',
comment: '发表评论',
uploadDatasetFile: '上传数据集文件',
importNewModel: '导入新模型',
completeWechatCodeScanningVerification: '完成微信扫码验证',
dailyRunCloudbrainTasks: '每日运行云脑任务',
datasetRecommendedByThePlatform: '数据集被平台推荐',
submitNewPublicImage: '提交新公开镜像',
imageRecommendedByThePlatform: '镜像被平台推荐',
firstChangeofAvatar: '首次更换头像',
dailyCommit: '每日commit',
calcPointDetails: '算力积分明细',
calcPointAcquisitionInstructions: '积分获取说明',
CurrAvailableCalcPoints: '当前可用算力积分(分)',
totalGainCalcPoints: '总获取算力积分(分)',
totalConsumeCalcPoints: '总消耗算力积分(分)',
gainDetail: '获取明细',
consumeDetail: '消耗明细',
serialNumber: '流水号',
time: '时间',
scene: '场景',
behaviorOfPoint: '积分行为',
explanation: '说明',
points: '积分',
status: '状态',
runTime: '运行时长',
taskName: '任务名称',

createdRepository: '创建了项目',
openedIssue: '创建了任务',
createdPullRequest: '创建了合并请求',
commentedOnIssue: '评论了任务',
uploadDataset: '上传了数据集文件',
createdNewModel: '导入了新模型',
firstBindingWechatRewards: '首次绑定微信奖励',
created: '创建了',
type: '类型',
dataset: '数据集',
setAsRecommendedDataset: '被设置为推荐数据集',
committedImage: '提交了镜像',
image: '镜像',
setAsRecommendedImage: '被设置为推荐镜像',
updatedAvatar: '更新了头像',
pushedBranch: '推送了{branch}分支代码到',
dailyMaxTips: '达到每日上限积分,不能拿满分',
memory: '内存',
sharedMemory: '共享内存',
';': ';',

noPointGainRecord: '还没有积分获取记录',
noPointConsumeRecord: '还没有积分消耗记录',
}

export default zh;

+ 16
- 0
web_src/vuepages/langs/index.js View File

@@ -0,0 +1,16 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import jsCookie from 'js-cookie';
import zh from './config/zh-CN';
import en from './config/en-US';

Vue.use(VueI18n);

export const lang = jsCookie.get('lang') || 'zh-CN';
export const i18n = new VueI18n({
locale: lang,
messages: {
'zh-CN': zh,
'en-US': en
},
});

+ 196
- 0
web_src/vuepages/pages/resources/components/QueueDialog.vue View File

@@ -0,0 +1,196 @@
<template>
<div class="base-dlg">
<BaseDialog :visible.sync="dialogShow" :width="`750px`" :title="type === 'add' ? `新建资源池(队列)` : '修改资源池(队列)'"
@open="open" @opened="opened" @close="close" @closed="closed">
<div class="dlg-content">
<div class="form">
<div class="form-row">
<div class="title required">
<span>资源池(队列)名称</span>
</div>
<div class="content">
<el-input v-model="point" placeholder=""></el-input>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>所属集群</span>
</div>
<div class="content">
<el-select :value="selectValue"></el-select>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>智算中心</span>
</div>
<div class="content">
<el-select :value="selectValue"></el-select>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>计算资源</span>
</div>
<div class="content">
<el-select :value="selectValue"></el-select>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>卡类型</span>
</div>
<div class="content">
<el-select :value="selectValue"></el-select>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>卡数</span>
</div>
<div class="content">
<el-input v-model="point" type="number" placeholder=""></el-input>
</div>
</div>
<div class="form-row" style="margin-top: 10px">
<div class="title"><span>备注</span></div>
<div class="content" style="width: 400px">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 4 }"
:placeholder="true ? '请输入充值操作备注' : '请输入扣减操作备注'" v-model="remark">
</el-input>
</div>
</div>
<div class="form-row" style="margin-top: 20px">
<div class="title"></div>
<div class="content">
<el-button type="primary" class="btn confirm-btn" @click="confirm">确定</el-button>
<el-button class="btn" @click="cancel">取消</el-button>
</div>
</div>
</div>
</div>
</BaseDialog>
</div>
</template>
<script>

import BaseDialog from '~/components/BaseDialog.vue';

export default {
name: "QueueDialog",
props: {
visible: { type: Boolean, default: false },
title: { type: String, default: '' },
type: { type: String, defalut: 'add' },
data: { type: Object, default: () => ({}) },
},
components: {
BaseDialog
},
data: function () {
return {
dialogShow: false,
dataInfo: {},
point: '',
remark: '',
selectValue: '',
};
},
watch: {
visible: function (val) {
this.dialogShow = val;
},
},
methods: {
open: function () {
if (this.type === 'add') {

} else if (this.type === 'edit') {

}
console.log('open', this.type, this.data);
this.$emit("open");
},
opened: function () {
this.$emit("opened");
},
close: function () {
this.$emit("close");
},
closed: function () {
this.$emit("closed");
this.$emit("update:visible", false);
},
confirm: function () {

},
cancel: function () {

}
},
};
</script>
<style scoped lang="less">
.dlg-content {
margin: 20px 0 25px 0;
display: flex;
justify-content: center;

.form {
width: 600px;

.form-row {
display: flex;
min-height: 42px;
margin-bottom: 4px;

.title {
width: 160px;
display: flex;
justify-content: flex-end;
align-items: center;
margin-right: 20px;
color: rgb(136, 136, 136);
font-size: 14px;

&.required {
span {
position: relative;
}

span::after {
position: absolute;
right: -10px;
top: -2px;
vertical-align: top;
content: '*';
color: #db2828;
}
}
}

.content {
width: 300px;
display: flex;
align-items: center;

/deep/ .el-select {
width: 100%;
}
}
}
}

.btn {
color: rgb(2, 0, 4);
background-color: rgb(194, 199, 204);
border-color: rgb(194, 199, 204);

&.confirm-btn {
color: #fff;
background-color: rgb(56, 158, 13);
border-color: rgb(56, 158, 13);
}
}
}
</style>

+ 194
- 0
web_src/vuepages/pages/resources/queue/index.vue View File

@@ -0,0 +1,194 @@
<template>
<div>
<div class="title"><span>资源池(队列)</span></div>
<div class="tools-bar">
<div>
<el-select class="select" size="medium" :value="value1">
<!-- <el-option v-for="item in sceneList" :key="item.k" :label="item.v" :value="item.k" /> -->
</el-select>
<el-select class="select" size="medium" :value="value1">
<!-- <el-option v-for="item in pointActions" :key="item.k" :label="item.v" :value="item.k" /> -->
</el-select>
<el-select class="select" size="medium" :value="value1"></el-select>
<el-select class="select" size="medium" :value="value1"></el-select>
</div>
<div>
<el-button size="medium" icon="el-icon-refresh" @click="showDialog('edit', {x: 1, y: 2, z: 3})">同步智算网络</el-button>
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')">新增资源池</el-button>
</div>
</div>
<div class="table-container">
<div>
<el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe>
<el-table-column prop="index" label="ID" align="center" header-align="center"></el-table-column>
<el-table-column prop="userName" label="资源池(队列)名称" align="center" header-align="center">
<template #default="scope">
<a :href="`/${scope.row.userName}`">{{ scope.row.userName }}</a>
</template>
</el-table-column>
<el-table-column prop="date" label="所属集群" align="center" header-align="center">
</el-table-column>
<el-table-column prop="scene" label="智算中心ID" align="center" header-align="center">
</el-table-column>
<el-table-column prop="pointAction" label="智算中心" align="left" header-align="center">
</el-table-column>
<el-table-column prop="remark" label="计算资源" align="left" header-align="center" min-width="200">
<template #default="scope">
<span v-html="scope.row.remark"></span>
</template>
</el-table-column>
<el-table-column prop="operator" label="卡类型" align="center" header-align="center">
</el-table-column>
<el-table-column prop="amount" label="卡数 " align="center" header-align="center">
<template #default="scope">
{{ scope.row.amount }}
</template>
</el-table-column>
<el-table-column prop="blance" label="最后更新时间" align="center" header-align="center">
<template #default="scope">
{{ scope.row.blance }}
</template>
</el-table-column>
<el-table-column prop="blance" label="备注" align="center" header-align="center">
<template #default="scope">
{{ scope.row.blance }}
</template>
</el-table-column>
<el-table-column prop="blance" label="操作" align="center" header-align="center">
<template #default="scope">
{{ scope.row.blance }}
</template>
</el-table-column>
<template #empty>
<span style="font-size: 12px">{{
loading ? '加载中...' : '暂无数据'
}}</span>
</template>
</el-table>
</div>
<div class="__r_p_pagination">
<div style="margin-top: 2rem">
<div class="center">
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage"
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total">
</el-pagination>
</div>
</div>
</div>
</div>
<QueueDialog :visible.sync="queueDialogShow" :type="queueDialogType" :data="queueDialogData" @confirm="queueDialogConfirm"></QueueDialog>
</div>
</template>

<script>
import QueueDialog from '../components/QueueDialog.vue';
export default {
data() {
return {
value1: '1',
searchVal: '',
loading: false,
tableData: [],
pageInfo: {
curpage: 1,
pageSize: 10,
pageSizes: [10],
total: 0,
},
queueDialogShow: false,
queueDialogType: 'add',
queueDialogData: {},
};
},
components: {
QueueDialog
},
methods: {
getTableData: function () {
this.tableData = new Array(20).fill(0).map((_, index) => {
return {
index: index,
};
})
},
currentChange: function (val) {
this.pageInfo.curpage = val;
// this.getTableData();
},
showDialog(type, data) {
this.queueDialogType = type;
this.queueDialogData = data || {};
this.queueDialogShow = true;
},
queueDialogConfirm() {
this.queueDialogShow = false;
}
},
mounted: function () {
this.getTableData();
},
beforeDestroy: function () {
},
};
</script>

<style scoped lang="less">
.title {
height: 30px;
display: flex;
align-items: center;
margin-bottom: 5px;

span {
font-weight: 700;
font-size: 16px;
color: rgb(16, 16, 16);
}
}

.tools-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;

.select {
margin-right: 10px;

/deep/ .el-input__inner {
border-radius: 0;
}
}
}

.table-container {
margin-bottom: 16px;

/deep/ .el-table__header {
th {
background: rgb(245, 245, 246);
font-size: 12px;
color: rgb(36, 36, 36);
}
}

/deep/ .el-table__body {
td {
font-size: 12px;
}
}

.op-btn {
cursor: pointer;
font-size: 12px;
color: rgb(25, 103, 252);
margin: 0 5px;
}
}

.center {
display: flex;
justify-content: center;
}
</style>

+ 17
- 0
web_src/vuepages/pages/resources/queue/vp-resources-queue.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 './index.vue';

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

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

+ 43
- 0
web_src/vuepages/pages/resources/scene/index.vue View File

@@ -0,0 +1,43 @@
<template>
<div class="xxx">
scene
</div>
</template>

<script>

export default {
data() {
return {
loading: false,
summaryInfo: {
available: 0,
gain: 0,
used: 0,
},
tabIndex: 0,
tableData: [],
pageInfo: {
curpage: 1,
pageSize: 10,
pageSizes: [10],
total: 0,
},
eventSource: null,
};
},
components: {},
methods: {
getTableData: function () {
},
},
mounted: function () {
},
beforeDestroy: function () {
},
};
</script>

<style scoped lang="less">

</style>

+ 17
- 0
web_src/vuepages/pages/resources/scene/vp-resources-scene.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 './index.vue';

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

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

+ 43
- 0
web_src/vuepages/pages/resources/specification/index.vue View File

@@ -0,0 +1,43 @@
<template>
<div class="xxx">
specification
</div>
</template>

<script>

export default {
data() {
return {
loading: false,
summaryInfo: {
available: 0,
gain: 0,
used: 0,
},
tabIndex: 0,
tableData: [],
pageInfo: {
curpage: 1,
pageSize: 10,
pageSizes: [10],
total: 0,
},
eventSource: null,
};
},
components: {},
methods: {
getTableData: function () {
},
},
mounted: function () {
},
beforeDestroy: function () {
},
};
</script>

<style scoped lang="less">

</style>

+ 17
- 0
web_src/vuepages/pages/resources/specification/vp-resources-specification.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 './index.vue';

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

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

+ 148
- 0
web_src/vuepages/pages/reward/point/utils.js View File

@@ -0,0 +1,148 @@

import { formatDate } from 'element-ui/lib/utils/date-util';
import { SOURCE_TYPE, CONSUME_STATUS, POINT_ACTIONS, JOB_TYPE } from '~/const';
import { i18n } from '~/langs';

const getSourceType = (key) => {
const find = SOURCE_TYPE.filter(item => item.k === key);
return find.length ? find[0].v : key;
};

const getConsumeStatus = (key) => {
const find = CONSUME_STATUS.filter(item => item.k === key);
return find.length ? find[0].v : key;
};

const getPointAction = (key) => {
const find = POINT_ACTIONS.filter(item => item.k === key);
return find.length ? find[0].v : key;
};

const getJobType = (key) => {
const find = JOB_TYPE.filter(item => item.k === key);
return find.length ? find[0].v : key;
};

const getJobTypeLink = (record, type) => {
let link = type === 'INCREASE' ? record.Action.RepoLink : '/' + record.Cloudbrain.RepoFullName;
const cloudbrain = type === 'INCREASE' ? record.Action?.Cloudbrain : record.Cloudbrain;
switch (cloudbrain?.JobType) {
case 'DEBUG':
if (cloudbrain.ComputeResource === 'CPU/GPU') {
link += `/cloudbrain/${cloudbrain.ID}`;
} else {
link += `/modelarts/notebook/${cloudbrain.ID}`;
}
break;
case 'TRAIN':
if (cloudbrain.Type === 1) {
link += `/modelarts/train-job/${cloudbrain.JobID}`;
} else if (cloudbrain.Type === 0) {
link += `/cloudbrain/train-job/${cloudbrain.JobID}`;
} else if (cloudbrain.Type === 2) {
link += `/grampus/train-job/${cloudbrain.JobID}`;
}
break;
case 'INFERENCE':
link += `/modelarts/inference-job/${cloudbrain.JobID}`;
break;
case 'BENCHMARK':
link += `/cloudbrain/benchmark/${cloudbrain.ID}`;
break;
default:
break;
};
return link;
};

export const getRewardPointRecordInfo = (record) => {
const out = {
sn: record.SerialNo,
date: formatDate(new Date(record.LastOperateDate * 1000), 'yyyy-MM-dd HH:mm:ss'),
_status: record.Status,
status: getConsumeStatus(record.Status) || '--',
statusColor: record.Status === 'OPERATING' ? 'rgb(33, 186, 69)' : '',
_sourceType: record.SourceType,
sourceType: getSourceType(record.SourceType),
duration: record?.Cloudbrain?.Duration || '--',
taskName: record?.Cloudbrain?.DisplayJobName || '--',
taskId: record?.Cloudbrain?.ID,
action: record?.Action?.OpType ? getPointAction(record.Action.OpType) : '--',
remark: record.Remark,
amount: record.Amount,
};
if (record.OperateType === 'INCREASE') {
if (record.SourceType === 'ADMIN_OPERATE') {
out.remark = record.Remark;
} else if (record.SourceType === 'ACCOMPLISH_TASK') {
switch (record?.Action?.OpType) {
case 1: // 创建公开项目 - 创建了项目OpenI/aiforge
out.remark = `${i18n.t('createdRepository')}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`;
break;
case 6: // 每日提出任务 - 创建了任务PCL-Platform.Intelligence/AISynergy#19
out.remark = `${i18n.t('openedIssue')}<a href="${record.Action.RepoLink}/issues/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`;
break;
case 7: // 每日提出PR - 创建了合并请求OpenI/aiforge#1
out.remark = `${i18n.t('createdPullRequest')}<a href="${record.Action.RepoLink}/pulls/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`;
break;
case 10: // 发表评论 - 评论了任务PCL-Platform.Intelligence/AISynergy#19
out.remark = `${i18n.t('commentedOnIssue')}<a href="${record.Action.CommentLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`;
break;
case 24: // 上传数据集文件 - 上传了数据集文件MMISTData.zip
out.remark = `${i18n.t('uploadDataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.RefName}</a>`;
break;
case 30: // 导入新模型 - 导入了新模型resnet50_qx7l
out.remark = `${i18n.t('createdNewModel')}<a href="${record.Action.RepoLink}/modelmanage/show_model_info?name=${record.Action.RefName}" rel="nofollow">${record.Action.RefName}</a>`;
break;
case 34: // 完成微信扫码验证 - 首次绑定微信奖励
out.remark = `${i18n.t('firstBindingWechatRewards')}`;
break;
case 35: // 每日运行云脑任务 - 创建了(CPU/GPU/NPU)类型(调试/训练/推理/评测)任务tangl202204131431995
out.remark = `${i18n.t('created')}${record.Action?.Cloudbrain?.ComputeResource}${i18n.t('type')}${getJobType(record.Action?.Cloudbrain?.JobType)} <a href="${getJobTypeLink(record, 'INCREASE')}" rel="nofollow">${record.Action.RefName}</a>`;
break;
case 36: // 数据集被平台推荐 - 数据集XXX被设置为推荐数据集
out.remark = `${i18n.t('dataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.Content && record.Action.Content.split('|')[1]}</a>${i18n.t('setAsRecommendedDataset')}`;
break;
case 37: // 提交新公开镜像 - 提交了镜像jiangxiang_ceshi_tang03
out.remark = `${i18n.t('committedImage')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>`;
break;
case 38: // 镜像被平台推荐 - 镜像XXX被设置为推荐镜像
out.remark = `${i18n.t('image')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>${i18n.t('setAsRecommendedImage')}`;
break;
case 39: // 首次更换头像 - 更新了头像
out.remark = `${i18n.t('updatedAvatar')}`;
break;
case 40: // 每日commit - 推送了xxxx分支的代码到OpenI/aiforge
const words = record.Action.RefName.split('/');
const branch = words[words.length - 1];
out.remark = `${i18n.t('pushedBranch', {
branch: `<a href="${record.Action.RepoLink}/src/branch/${branch}" rel="nofollow">${branch}</a>`
})}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`;
break;
default:
break;
}
} else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') {
//
}
if (record.LossAmount !== 0) {
out.amount = record.Amount;
out.remark += `${out.remark ? i18n.t(';') : ''}${i18n.t('dailyMaxTips')}`;
}
} else if (record.OperateType === 'DECREASE') {
if (record.SourceType === 'ADMIN_OPERATE') {
out.remark = record.Remark;
} else if (record.SourceType === 'ACCOMPLISH_TASK') {
//
} else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') {
out.taskName = `<a href="${getJobTypeLink(record, 'DECREASE')}" rel="nofollow">${record?.Cloudbrain?.DisplayJobName}</a>`;
if (record?.Cloudbrain?.ComputeResource === 'CPU/GPU') {
const resourceSpec = record?.Cloudbrain?.ResourceSpec?.ResourceSpec;
out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【GPU: ${resourceSpec?.gpu}, CPU: ${resourceSpec?.cpu}, ${i18n.t('memory')}: ${(resourceSpec?.memMiB / 1024).toFixed(2)}GB, ${i18n.t('sharedMemory')}: ${(resourceSpec?.shareMemMiB / 1024).toFixed(2)}GB】`;
} else {
out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【${record?.Cloudbrain?.ResourceSpec.FlavorInfo.desc}】`;
}
}
}
return out;
};

+ 16
- 0
web_src/vuepages/pages/reward/point/vp-point.js View File

@@ -0,0 +1,16 @@
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 './vp-point.vue';

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

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

+ 308
- 0
web_src/vuepages/pages/reward/point/vp-point.vue View File

@@ -0,0 +1,308 @@
<template>
<div class="__reward-pointer-c">
<div class="ui container" style="width:80%;min-width:1200px;">
<div class="__r_p_header">
<div>
<p class="__title">{{ $t('calcPointDetails') }}</p>
</div>
<div style="padding: 0 5px; font-size: 14px">
<span>
<i class="question circle icon link" style="color: rgba(3, 102, 214, 1)" data-position="right center"
data-variation="mini"></i>
<a href="/reward/point/rule" target="_blank" style="color: rgba(3, 102, 214, 1)">{{
$t('calcPointAcquisitionInstructions')
}}</a>
</span>
</div>
</div>
<div class="__r_p_summary">
<div class="__r_p_summary_item-c __flex-1">
<div class="__val">{{ summaryInfo.available }}</div>
<div class="__exp">{{ $t('CurrAvailableCalcPoints') }}</div>
</div>
<div class="__r_p_summary_line"></div>
<div class="__r_p_summary_item-c __flex-1">
<div class="__val">{{ summaryInfo.gain }}</div>
<div class="__exp">{{ $t('totalGainCalcPoints') }}</div>
</div>
<div class="__r_p_summary_item-c __flex-1">
<div class="__val">{{ summaryInfo.used }}</div>
<div class="__exp">{{ $t('totalConsumeCalcPoints') }}</div>
</div>
</div>
<div class="__r_p_tab">
<div class="__r_p_tab-item" :class="tabIndex === 0 ? '__focus' : ''" style="border-radius: 5px 0px 0px 5px"
@click="tabChange(0)">
{{ $t('gainDetail') }}
</div>
<div class="__r_p_tab-item" :class="tabIndex === 1 ? '__focus' : ''" style="border-radius: 0px 5px 5px 0px"
@click="tabChange(1)">
{{ $t('consumeDetail') }}
</div>
</div>
<div class="__r_p_table">
<div v-show="tabIndex === 0">
<el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe
v-if="tableData.length">
<el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center"
width="180">
</el-table-column>
<el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center"
width="180">
</el-table-column>
<el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center"
header-align="center" width="180"></el-table-column>
<el-table-column column-key="action" prop="action" :label="$t('behaviorOfPoint')" align="center"
header-align="center" width="200"></el-table-column>
<el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200"
header-align="center">
<template slot-scope="scope">
<span v-html="scope.row.remark"></span>
</template>
</el-table-column>
<el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center"
header-align="center" width="120"></el-table-column>
<template slot="empty">
<span>{{ loading ? $t('loading') : $t('noData') }}</span>
</template>
</el-table>
<el-empty v-else :image-size="140" :description="$t('noPointGainRecord')"></el-empty>
</div>
<div v-show="tabIndex === 1">
<el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe
v-if="tableData.length">
<el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center"
width="180">
</el-table-column>
<el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center"
width="180">
</el-table-column>
<el-table-column column-key="status" prop="status" :label="$t('status')" align="center"
header-align="center" width="120">
<template slot-scope="scope">
<span :style="{ color: scope.row.statusColor }">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center"
header-align="center" width="180"></el-table-column>
<el-table-column column-key="duration" prop="duration" :label="$t('runTime')" align="center"
header-align="center" width="120"></el-table-column>
<el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200"
header-align="center">
<template slot-scope="scope">
<span v-html="scope.row.remark"></span>
</template>
</el-table-column>
<el-table-column column-key="taskName" prop="taskName" :label="$t('taskName')" align="center"
header-align="center" width="180">
<template slot-scope="scope">
<span v-html="scope.row.taskName"></span>
</template>
</el-table-column>
<el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center"
header-align="center" width="120"></el-table-column>
<template slot="empty">
<span>{{ loading ? $t('loading') : $t('noData') }}</span>
</template>
</el-table>
<el-empty v-else :image-size="140" :description="$t('noPointConsumeRecord')"></el-empty>
</div>
<div class="__r_p_pagination" v-if="tableData.length">
<div style="margin-top: 2rem">
<div class="center">
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage"
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total">
</el-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import { getPoint, getPointAccount, getPointList } from "~/apis/modules/point";
import { getRewardPointRecordInfo } from './utils';
export default {
data() {
return {
loading: false,
summaryInfo: {
available: 0,
gain: 0,
used: 0,
},
tabIndex: 0,
tableData: [],
pageInfo: {
curpage: 1,
pageSize: 10,
pageSizes: [10],
total: 0,
},
eventSource: null,
};
},
components: {},
methods: {
currentChange: function (val) {
this.pageInfo.curpage = val;
this.getTableData();
},
tabChange: function (index) {
if (this.tabIndex === index) return;
this.tabIndex = index;
this.pageInfo.curpage = 1;
this.pageInfo.total = 0;
this.getTableData();
},
getSummaryInfo: function () {
getPointAccount().then(res => {
if (res.data && res.data.Code === 0) {
const data = res.data.Data;
this.summaryInfo.available = data.Balance;
this.summaryInfo.gain = data.TotalEarned;
this.summaryInfo.used = data.TotalConsumed;
}
}).catch(err => {
console.log(err);
})
},
getTableData: function () {
this.loading = true;
getPointList({
Operate: this.tabIndex === 0 ? 'INCREASE' : 'DECREASE',
Page: this.pageInfo.curpage,
// pageSize: this.pageInfo.pageSize,
}).then((res) => {
this.loading = false;
const tableData = [];
if (res.data && res.data.Code === 0) {
const data = res.data.Data;
const records = data.Records;
for (let i = 0, iLen = records.length; i < iLen; i++) {
const record = records[i];
tableData.push(getRewardPointRecordInfo(record));
}
this.tableData.splice(0, Infinity, ...tableData);
this.pageInfo.total = data.Total;
}
})
.catch((err) => {
console.log(err);
this.loading = false;
this.tableData.splice(0, Infinity);
});
},
},
mounted: function () {
this.getSummaryInfo();
this.getTableData();
const { AppSubUrl, csrf, NotificationSettings } = window.config;
if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) {
const source = new EventSource(`${AppSubUrl}/user/events`);
source.addEventListener('reward-operation', (e) => {
try {
this.getSummaryInfo();
this.getTableData();
} catch (err) {
console.error(err);
}
});
this.eventSource = source;
}
},
beforeDestroy: function () {
this.eventSource && this.eventSource.close();
},
};
</script>

<style scoped lang="less">
.__flex-1 {
flex: 1;
}

.__reward-pointer-c {
.__r_p_header {
height: 30px;
margin: 10px 0;
display: flex;
align-items: center;
justify-content: space-between;

.__title {
font-weight: 400;
font-size: 18px;
color: rgb(16, 16, 16);
line-height: 26px;
}
}

.__r_p_summary {
display: flex;
align-items: center;
height: 100px;
background-color: rgb(245, 245, 246);

.__r_p_summary_item-c {
.__val {
text-align: center;
margin: 12px 0;
font-weight: 400;
font-size: 28px;
color: rgb(16, 16, 16);
}

.__exp {
text-align: center;
font-weight: 400;
font-size: 14px;
color: rgba(54, 56, 64, 1);
}
}
}
}

.__r_p_summary_line {
width: 1px;
height: 80%;
background-color: rgb(212, 212, 213);
}

.__r_p_tab {
display: flex;
margin: 18px 0;

.__r_p_tab-item {
width: 115px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid rgb(225, 227, 230);
color: #101010;
box-sizing: border-box;
cursor: pointer;

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

.__r_p_table {
/deep/ .el-table__header {
th {
background: rgb(245, 245, 246);
color: rgb(96, 98, 102);
font-weight: 400;
font-size: 14px;
}
}
}
</style>

+ 7
- 0
webpack.config.js View File

@@ -29,6 +29,11 @@ for (const path of stadalonePaths) {
standalone[parse(path).name] = [path];
}

const vuePages = {};
for (const path of glob('web_src/vuepages/**/vp-*.js')) {
vuePages[parse(path).name] = [path];
}

const isProduction = process.env.NODE_ENV !== 'development';

module.exports = {
@@ -44,6 +49,7 @@ module.exports = {
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
...standalone,
...themes,
...vuePages,
},
devtool: false,
output: {
@@ -267,6 +273,7 @@ module.exports = {
symlinks: false,
alias: {
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only
'~': resolve(__dirname, 'web_src/vuepages'),
},
extensions: ['.tsx', '.ts', '.js']
},


+ 7
- 0
webpack_pro.config.js View File

@@ -29,6 +29,11 @@ for (const path of stadalonePaths) {
standalone[parse(path).name] = [path];
}

const vuePages = {};
for (const path of glob('web_src/vuepages/**/vp-*.js')) {
vuePages[parse(path).name] = [path];
}

const isProduction = process.env.NODE_ENV !== 'development';

module.exports = {
@@ -44,6 +49,7 @@ module.exports = {
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'),
...standalone,
...themes,
...vuePages
},
devtool: false,
output: {
@@ -267,6 +273,7 @@ module.exports = {
symlinks: false,
alias: {
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only
'~': resolve(__dirname, 'web_src/vuepages'),
},
extensions: ['.tsx', '.ts', '.js']
},


Loading…
Cancel
Save