Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1372pull/1411/head
@@ -36,6 +36,7 @@ type AiModelManage struct { | |||
CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||
IsCanOper bool | |||
IsCanDelete bool | |||
} | |||
type AiModelQueryOptions struct { | |||
@@ -33,6 +33,7 @@ const ( | |||
JobTypeSnn4imagenet JobType = "SNN4IMAGENET" | |||
JobTypeBrainScore JobType = "BRAINSCORE" | |||
JobTypeTrain JobType = "TRAIN" | |||
JobTypeInference JobType = "INFERENCE" | |||
//notebook | |||
ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 | |||
@@ -111,7 +112,7 @@ type Cloudbrain struct { | |||
ComputeResource string //计算资源,例如npu | |||
EngineID int64 //引擎id | |||
TrainUrl string //输出的obs路径 | |||
TrainUrl string //输出模型的obs路径 | |||
BranchName string //分支名称 | |||
Parameters string //传给modelarts的param参数 | |||
BootFile string //启动文件 | |||
@@ -125,6 +126,12 @@ type Cloudbrain struct { | |||
EngineName string //引擎名称 | |||
TotalVersionCount int //任务的所有版本数量,包括删除的 | |||
LabelName string //标签名称 | |||
ModelName string //模型名称 | |||
ModelVersion string //模型版本 | |||
CkptName string //权重文件名称 | |||
ResultUrl string //推理结果的obs路径 | |||
User *User `xorm:"-"` | |||
Repo *Repository `xorm:"-"` | |||
} | |||
@@ -207,7 +214,7 @@ type CloudbrainsOptions struct { | |||
CloudbrainIDs []int64 | |||
// JobStatus CloudbrainStatus | |||
Type int | |||
JobType string | |||
JobTypes []string | |||
VersionName string | |||
IsLatestVersion string | |||
JobTypeNot bool | |||
@@ -644,6 +651,25 @@ type Config struct { | |||
Flavor Flavor `json:"flavor"` | |||
PoolID string `json:"pool_id"` | |||
} | |||
type CreateInferenceJobParams struct { | |||
JobName string `json:"job_name"` | |||
Description string `json:"job_desc"` | |||
InfConfig InfConfig `json:"config"` | |||
WorkspaceID string `json:"workspace_id"` | |||
} | |||
type InfConfig struct { | |||
WorkServerNum int `json:"worker_server_num"` | |||
AppUrl string `json:"app_url"` //训练作业的代码目录 | |||
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下 | |||
Parameter []Parameter `json:"parameter"` | |||
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL | |||
EngineID int64 `json:"engine_id"` | |||
LogUrl string `json:"log_url"` | |||
CreateVersion bool `json:"create_version"` | |||
Flavor Flavor `json:"flavor"` | |||
PoolID string `json:"pool_id"` | |||
} | |||
type CreateTrainJobVersionParams struct { | |||
Description string `json:"job_desc"` | |||
@@ -894,14 +920,14 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { | |||
) | |||
} | |||
if (opts.JobType) != "" { | |||
if len(opts.JobTypes) > 0 { | |||
if opts.JobTypeNot { | |||
cond = cond.And( | |||
builder.Neq{"cloudbrain.job_type": opts.JobType}, | |||
builder.NotIn("cloudbrain.job_type", opts.JobTypes), | |||
) | |||
} else { | |||
cond = cond.And( | |||
builder.Eq{"cloudbrain.job_type": opts.JobType}, | |||
builder.In("cloudbrain.job_type", opts.JobTypes), | |||
) | |||
} | |||
} | |||
@@ -978,6 +1004,7 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) { | |||
cond = cond.And( | |||
builder.Eq{"job_type": "TRAIN"}, | |||
) | |||
cloudbrains := make([]*CloudbrainInfo, 0) | |||
if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC"). | |||
Find(&cloudbrains); err != nil { | |||
@@ -1025,9 +1052,9 @@ func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, e | |||
) | |||
} | |||
if (opts.JobType) != "" { | |||
if len(opts.JobTypes) > 0 { | |||
cond = cond.And( | |||
builder.Eq{"cloudbrain.job_type": opts.JobType}, | |||
builder.In("cloudbrain.job_type", opts.JobTypes), | |||
) | |||
} | |||
@@ -1211,6 +1238,22 @@ func GetCloudbrainTrainJobCountByUserID(userID int64) (int, error) { | |||
return int(count), err | |||
} | |||
func GetCloudbrainInferenceJobCountByUserID(userID int64) (int, error) { | |||
count, err := x.In("status", ModelArtsTrainJobInit, ModelArtsTrainJobImageCreating, ModelArtsTrainJobSubmitTrying, ModelArtsTrainJobWaiting, ModelArtsTrainJobRunning, ModelArtsTrainJobScaling, ModelArtsTrainJobCheckInit, ModelArtsTrainJobCheckRunning, ModelArtsTrainJobCheckRunningCompleted). | |||
And("job_type = ? and user_id = ? and type = ?", JobTypeInference, userID, TypeCloudBrainTwo).Count(new(Cloudbrain)) | |||
return int(count), err | |||
} | |||
func UpdateInferenceJob(job *Cloudbrain) error { | |||
return updateInferenceJob(x, job) | |||
} | |||
func updateInferenceJob(e Engine, job *Cloudbrain) error { | |||
var sess *xorm.Session | |||
sess = e.Where("job_id = ?", job.JobID) | |||
_, err := sess.Cols("status", "train_job_duration").Update(job) | |||
return err | |||
} | |||
func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) { | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
@@ -45,6 +45,30 @@ type CreateModelArtsTrainJobForm struct { | |||
EngineName string `form:"engine_names" binding:"Required"` | |||
} | |||
type CreateModelArtsInferenceJobForm struct { | |||
JobName string `form:"job_name" binding:"Required"` | |||
Attachment string `form:"attachment" binding:"Required"` | |||
BootFile string `form:"boot_file" binding:"Required"` | |||
WorkServerNumber int `form:"work_server_number" binding:"Required"` | |||
EngineID int `form:"engine_id" binding:"Required"` | |||
PoolID string `form:"pool_id" binding:"Required"` | |||
Flavor string `form:"flavor" binding:"Required"` | |||
Params string `form:"run_para_list" binding:"Required"` | |||
Description string `form:"description"` | |||
IsSaveParam string `form:"is_save_para"` | |||
ParameterTemplateName string `form:"parameter_template_name"` | |||
PrameterDescription string `form:"parameter_description"` | |||
BranchName string `form:"branch_name" binding:"Required"` | |||
VersionName string `form:"version_name" binding:"Required"` | |||
FlavorName string `form:"flaver_names" binding:"Required"` | |||
EngineName string `form:"engine_names" binding:"Required"` | |||
LabelName string `form:"label_names" binding:"Required"` | |||
TrainUrl string `form:"train_url" binding:"Required"` | |||
ModelName string `form:"model_name" binding:"Required"` | |||
ModelVersion string `form:"model_version" binding:"Required"` | |||
CkptName string `form:"ckpt_name" binding:"Required"` | |||
} | |||
func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} |
@@ -38,6 +38,7 @@ const ( | |||
// "]}" | |||
CodePath = "/code/" | |||
OutputPath = "/output/" | |||
ResultPath = "/result/" | |||
LogPath = "/log/" | |||
JobPath = "/job/" | |||
OrderDesc = "desc" //向下查询 | |||
@@ -45,6 +46,8 @@ const ( | |||
Lines = 500 | |||
TrainUrl = "train_url" | |||
DataUrl = "data_url" | |||
ResultUrl = "result_url" | |||
CkptUrl = "ckpt_url" | |||
PerPage = 10 | |||
IsLatestVersion = "1" | |||
NotLatestVersion = "0" | |||
@@ -113,6 +116,36 @@ type GenerateTrainJobVersionReq struct { | |||
TotalVersionCount int | |||
} | |||
type GenerateInferenceJobReq struct { | |||
JobName string | |||
Uuid string | |||
Description string | |||
CodeObsPath string | |||
BootFile string | |||
BootFileUrl string | |||
DataUrl string | |||
TrainUrl string | |||
FlavorCode string | |||
LogUrl string | |||
PoolID string | |||
WorkServerNumber int | |||
EngineID int64 | |||
Parameters []models.Parameter | |||
CommitID string | |||
Params string | |||
BranchName string | |||
FlavorName string | |||
EngineName string | |||
LabelName string | |||
IsLatestVersion string | |||
VersionCount int | |||
TotalVersionCount int | |||
ModelName string | |||
ModelVersion string | |||
CkptName string | |||
ResultUrl string | |||
} | |||
type VersionInfo struct { | |||
Version []struct { | |||
ID int `json:"id"` | |||
@@ -329,12 +362,14 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job | |||
return err | |||
} | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeTrain)) | |||
repo := ctx.Repo.Repository | |||
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobType: string(models.JobTypeTrain), | |||
JobID: strconv.FormatInt(jobResult.JobID, 10), | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobTypes: jobTypes, | |||
JobID: strconv.FormatInt(jobResult.JobID, 10), | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Cloudbrain", err) | |||
@@ -441,8 +476,82 @@ func TransTrainJobStatus(status int) string { | |||
} | |||
} | |||
func GetVersionOutputPathByTotalVersionCount(TotalVersionCount int) (VersionOutputPath string) { | |||
func GetOutputPathByCount(TotalVersionCount int) (VersionOutputPath string) { | |||
talVersionCountToString := fmt.Sprintf("%04d", TotalVersionCount) | |||
VersionOutputPath = "V" + talVersionCountToString | |||
return VersionOutputPath | |||
} | |||
func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (err error) { | |||
jobResult, err := createInferenceJob(models.CreateInferenceJobParams{ | |||
JobName: req.JobName, | |||
Description: req.Description, | |||
InfConfig: models.InfConfig{ | |||
WorkServerNum: req.WorkServerNumber, | |||
AppUrl: req.CodeObsPath, | |||
BootFileUrl: req.BootFileUrl, | |||
DataUrl: req.DataUrl, | |||
EngineID: req.EngineID, | |||
// TrainUrl: req.TrainUrl, | |||
LogUrl: req.LogUrl, | |||
PoolID: req.PoolID, | |||
CreateVersion: true, | |||
Flavor: models.Flavor{ | |||
Code: req.FlavorCode, | |||
}, | |||
Parameter: req.Parameters, | |||
}, | |||
}) | |||
if err != nil { | |||
log.Error("CreateJob failed: %v", err.Error()) | |||
return err | |||
} | |||
attach, err := models.GetAttachmentByUUID(req.Uuid) | |||
if err != nil { | |||
log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error()) | |||
return err | |||
} | |||
err = models.CreateCloudbrain(&models.Cloudbrain{ | |||
Status: TransTrainJobStatus(jobResult.Status), | |||
UserID: ctx.User.ID, | |||
RepoID: ctx.Repo.Repository.ID, | |||
JobID: strconv.FormatInt(jobResult.JobID, 10), | |||
JobName: req.JobName, | |||
JobType: string(models.JobTypeInference), | |||
Type: models.TypeCloudBrainTwo, | |||
VersionID: jobResult.VersionID, | |||
VersionName: jobResult.VersionName, | |||
Uuid: req.Uuid, | |||
DatasetName: attach.Name, | |||
CommitID: req.CommitID, | |||
EngineID: req.EngineID, | |||
TrainUrl: req.TrainUrl, | |||
BranchName: req.BranchName, | |||
Parameters: req.Params, | |||
BootFile: req.BootFile, | |||
DataUrl: req.DataUrl, | |||
LogUrl: req.LogUrl, | |||
FlavorCode: req.FlavorCode, | |||
Description: req.Description, | |||
WorkServerNumber: req.WorkServerNumber, | |||
FlavorName: req.FlavorName, | |||
EngineName: req.EngineName, | |||
LabelName: req.LabelName, | |||
IsLatestVersion: req.IsLatestVersion, | |||
VersionCount: req.VersionCount, | |||
TotalVersionCount: req.TotalVersionCount, | |||
ModelName: req.ModelName, | |||
ModelVersion: req.ModelVersion, | |||
CkptName: req.CkptName, | |||
ResultUrl: req.ResultUrl, | |||
}) | |||
if err != nil { | |||
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error()) | |||
return err | |||
} | |||
return nil | |||
} |
@@ -874,3 +874,59 @@ sendjob: | |||
return &result, nil | |||
} | |||
func createInferenceJob(createJobParams models.CreateInferenceJobParams) (*models.CreateTrainJobResult, error) { | |||
checkSetting() | |||
client := getRestyClient() | |||
var result models.CreateTrainJobResult | |||
retry := 0 | |||
sendjob: | |||
res, err := client.R(). | |||
SetHeader("Content-Type", "application/json"). | |||
SetAuthToken(TOKEN). | |||
SetBody(createJobParams). | |||
SetResult(&result). | |||
Post(HOST + "/v1/" + setting.ProjectID + urlTrainJob) | |||
if err != nil { | |||
return nil, fmt.Errorf("resty create inference-job: %s", err) | |||
} | |||
req, _ := json.Marshal(createJobParams) | |||
log.Info("%s", req) | |||
if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||
retry++ | |||
_ = getToken() | |||
goto sendjob | |||
} | |||
if res.StatusCode() != http.StatusOK { | |||
var temp models.ErrorResult | |||
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil { | |||
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error()) | |||
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error()) | |||
} | |||
log.Error("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
BootFileErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.BootFileUrl + "'." | |||
DataSetErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.DataUrl + "'." | |||
if temp.ErrorMsg == BootFileErrorMsg { | |||
log.Error("启动文件错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
return &result, fmt.Errorf("启动文件错误!") | |||
} | |||
if temp.ErrorMsg == DataSetErrorMsg { | |||
log.Error("数据集错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
return &result, fmt.Errorf("数据集错误!") | |||
} | |||
return &result, fmt.Errorf("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
} | |||
if !result.IsSuccess { | |||
log.Error("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||
return &result, fmt.Errorf("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||
} | |||
return &result, nil | |||
} |
@@ -28,6 +28,13 @@ type FileInfo struct { | |||
ParenDir string `json:"ParenDir"` | |||
UUID string `json:"UUID"` | |||
} | |||
type FileInfoList []FileInfo | |||
func (ulist FileInfoList) Swap(i, j int) { ulist[i], ulist[j] = ulist[j], ulist[i] } | |||
func (ulist FileInfoList) Len() int { return len(ulist) } | |||
func (ulist FileInfoList) Less(i, j int) bool { | |||
return strings.Compare(ulist[i].FileName, ulist[j].FileName) > 0 | |||
} | |||
//check if has the object | |||
func ObsHasObject(path string) (bool, error) { | |||
@@ -333,7 +340,8 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er | |||
input.MaxKeys = 100 | |||
input.Prefix = prefix | |||
index := 1 | |||
fileInfos := make([]FileInfo, 0) | |||
fileInfoList := FileInfoList{} | |||
prefixLen := len(prefix) | |||
log.Info("prefix=" + input.Prefix) | |||
for { | |||
@@ -358,7 +366,7 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er | |||
IsDir: isDir, | |||
ParenDir: "", | |||
} | |||
fileInfos = append(fileInfos, fileInfo) | |||
fileInfoList = append(fileInfoList, fileInfo) | |||
} | |||
if output.IsTruncated { | |||
input.Marker = output.NextMarker | |||
@@ -373,13 +381,14 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er | |||
return nil, err | |||
} | |||
} | |||
return fileInfos, nil | |||
sort.Sort(fileInfoList) | |||
return fileInfoList, nil | |||
} | |||
func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) { | |||
func GetObsListObject(jobName, outPutPath, parentDir, versionName string) ([]FileInfo, error) { | |||
input := &obs.ListObjectsInput{} | |||
input.Bucket = setting.Bucket | |||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName, parentDir), "/") | |||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, outPutPath, versionName, parentDir), "/") | |||
strPrefix := strings.Split(input.Prefix, "/") | |||
output, err := ObsCli.ListObjects(input) | |||
fileInfos := make([]FileInfo, 0) | |||
@@ -401,7 +410,7 @@ func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error | |||
nextParentDir = parentDir + "/" + fileName | |||
} | |||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath { | |||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == outPutPath { | |||
continue | |||
} | |||
} else { | |||
@@ -630,7 +630,7 @@ oauth2_application_create_description = OAuth2 applications gives your third-par | |||
oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? | |||
authorized_oauth2_applications = Authorized OAuth2 Applications | |||
authorized_oauth2_applications_description = You've granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed. | |||
authorized_oauth2_applications_description = You have granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed. | |||
revoke_key = Revoke | |||
revoke_oauth2_grant = Revoke Access | |||
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? | |||
@@ -869,6 +869,7 @@ modelarts.notebook=Debug Task | |||
modelarts.train_job=Train Task | |||
modelarts.train_job.new_debug= New Debug Task | |||
modelarts.train_job.new_train=New Train Task | |||
modelarts.train_job.new_infer=New Inference Task | |||
modelarts.train_job.config=Configuration information | |||
modelarts.train_job.new=New train Task | |||
modelarts.train_job.new_place=The description should not exceed 256 characters | |||
@@ -882,6 +883,8 @@ modelarts.parent_version=Parent Version | |||
modelarts.run_version=Run Version | |||
modelarts.train_job.compute_node=Compute Node | |||
modelarts.create_model = Create Model | |||
modelarts.model_label=Model Label | |||
modelarts.infer_dataset = Inference Dataset | |||
modelarts.train_job.basic_info=Basic Info | |||
@@ -928,6 +931,13 @@ modelarts.train_job_para_admin=train_job_para_admin | |||
modelarts.train_job_para.edit=train_job_para.edit | |||
modelarts.train_job_para.connfirm=train_job_para.connfirm | |||
modelarts.infer_job_model = Model | |||
modelarts.infer_job_model_file = Model File | |||
modelarts.infer_job = Inference Job | |||
modelarts.infer_job.model_version = Model/Version | |||
modelarts.infer_job.select_model = Select Model | |||
modelarts.infer_job.tooltip = The model has been deleted and cannot be viewed. | |||
model.manage.import_new_model=Import New Model | |||
model.manage.create_error=Equal Name and Version has existed. | |||
model.manage.model_name = Model Name | |||
@@ -831,7 +831,7 @@ debug=调试 | |||
debug_again=再次调试 | |||
stop=停止 | |||
delete=删除 | |||
model_download=模型下载 | |||
model_download=结果下载 | |||
submit_image=提交镜像 | |||
download=模型下载 | |||
@@ -876,9 +876,10 @@ modelarts.notebook=调试任务 | |||
modelarts.train_job=训练任务 | |||
modelarts.train_job.new_debug=新建调试任务 | |||
modelarts.train_job.new_train=新建训练任务 | |||
modelarts.train_job.new_infer=新建推理任务 | |||
modelarts.train_job.config=配置信息 | |||
modelarts.train_job.new=新建训练任务 | |||
modelarts.train_job.new_place=描述字数不超过256个字符 | |||
modelarts.train_job.new_place=描述字数不超过255个字符 | |||
modelarts.model_name=模型名称 | |||
modelarts.model_size=模型大小 | |||
modelarts.import_model=导入模型 | |||
@@ -888,6 +889,8 @@ modelarts.current_version=当前版本 | |||
modelarts.parent_version=父版本 | |||
modelarts.run_version=运行版本 | |||
modelarts.create_model=创建模型 | |||
modelarts.model_label=模型标签 | |||
modelarts.infer_dataset = 推理数据集 | |||
@@ -929,7 +932,7 @@ modelarts.train_job.NAS_mount_path=NAS挂载路径 | |||
modelarts.train_job.query_whether_save_parameter=保存作业参数 | |||
modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。 | |||
modelarts.train_job.common_frame=常用框架 | |||
modelarts.train_job.amount_of_compute_node=计算节点个数 | |||
modelarts.train_job.amount_of_compute_node=计算节点数 | |||
modelarts.train_job.job_parameter_name=任务参数名称 | |||
modelarts.train_job.parameter_description=任务参数描述 | |||
modelarts.log=日志 | |||
@@ -939,6 +942,14 @@ modelarts.train_job_para_admin=任务参数管理 | |||
modelarts.train_job_para.edit=编辑 | |||
modelarts.train_job_para.connfirm=确定 | |||
modelarts.infer_job_model = 模型名称 | |||
modelarts.infer_job_model_file = 模型文件 | |||
modelarts.infer_job = 推理任务 | |||
modelarts.infer_job.model_version = 模型/版本 | |||
modelarts.infer_job.select_model = 选择模型 | |||
modelarts.infer_job.boot_file_helper=启动文件是您程序执行的入口文件,必须是以.py结尾的文件。比如inference.py、main.py、example/inference.py、case/main.py。 | |||
modelarts.infer_job.tooltip = 该模型已删除,无法查看。 | |||
model.manage.import_new_model=导入新模型 | |||
model.manage.create_error=相同的名称和版本的模型已经存在。 | |||
model.manage.model_name = 模型名称 | |||
@@ -247,7 +247,7 @@ var actionNameZH={ | |||
"14":"关闭了合并请求", | |||
"15":"重新开启了合并请求", | |||
"17":"从 {repoName} 删除分支 {deleteBranchName}", | |||
"22":"拒绝了合并请求", | |||
"22":"建议变更", | |||
"23":"评论了合并请求" | |||
}; | |||
@@ -265,7 +265,7 @@ var actionNameEN={ | |||
"14":" closed pull request", | |||
"15":" reopened pull request", | |||
"17":" deleted branch {deleteBranchName} from {repoName}", | |||
"22":" rejected pull request", | |||
"22":" proposed changes", | |||
"23":" commented on pull request" | |||
}; | |||
@@ -893,6 +893,15 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/model_list", repo.ModelList) | |||
}) | |||
}) | |||
m.Group("/inference-job", func() { | |||
m.Group("/:jobid", func() { | |||
m.Get("", repo.GetModelArtsInferenceJob) | |||
m.Get("/log", repo.TrainJobGetLog) | |||
m.Post("/del_version", repo.DelTrainJobVersion) | |||
m.Post("/stop_version", repo.StopTrainJobVersion) | |||
m.Get("/result_list", repo.ResultList) | |||
}) | |||
}) | |||
}, reqRepoReader(models.UnitTypeCloudBrain)) | |||
}, repoAssignment()) | |||
}) | |||
@@ -133,7 +133,6 @@ func TrainJobGetLog(ctx *context.APIContext) { | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
// var logFileName = ctx.Query("file_name") | |||
var baseLine = ctx.Query("base_line") | |||
var order = ctx.Query("order") | |||
var lines = ctx.Query("lines") | |||
@@ -222,12 +221,14 @@ func DelTrainJobVersion(ctx *context.APIContext) { | |||
} | |||
//获取删除后的版本数量 | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeTrain)) | |||
repo := ctx.Repo.Repository | |||
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobType: string(models.JobTypeTrain), | |||
JobID: jobID, | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobTypes: jobTypes, | |||
JobID: jobID, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("get VersionListCount failed", err) | |||
@@ -299,7 +300,80 @@ func ModelList(ctx *context.APIContext) { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
models, err := storage.GetObsListObject(task.JobName, parentDir, versionName) | |||
models, err := storage.GetObsListObject(task.JobName, "output/", parentDir, versionName) | |||
if err != nil { | |||
log.Info("get TrainJobListModel failed:", err) | |||
ctx.ServerError("GetObsListObject:", err) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
"JobID": jobID, | |||
"VersionName": versionName, | |||
"StatusOK": 0, | |||
"Path": dirArray, | |||
"Dirs": models, | |||
"task": task, | |||
"PageIsCloudBrain": true, | |||
}) | |||
} | |||
func GetModelArtsInferenceJob(ctx *context.APIContext) { | |||
var ( | |||
err error | |||
) | |||
jobID := ctx.Params(":jobid") | |||
job, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.NotFound(err) | |||
return | |||
} | |||
result, err := modelarts.GetTrainJob(jobID, strconv.FormatInt(job.VersionID, 10)) | |||
if err != nil { | |||
ctx.NotFound(err) | |||
return | |||
} | |||
job.Status = modelarts.TransTrainJobStatus(result.IntStatus) | |||
job.Duration = result.Duration | |||
job.TrainJobDuration = result.TrainJobDuration | |||
if result.Duration != 0 { | |||
job.TrainJobDuration = util.AddZero(result.Duration/3600000) + ":" + util.AddZero(result.Duration%3600000/60000) + ":" + util.AddZero(result.Duration%60000/1000) | |||
} else { | |||
job.TrainJobDuration = "00:00:00" | |||
} | |||
err = models.UpdateInferenceJob(job) | |||
if err != nil { | |||
log.Error("UpdateJob failed:", err) | |||
} | |||
ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
"JobID": jobID, | |||
"JobStatus": job.Status, | |||
"JobDuration": job.TrainJobDuration, | |||
}) | |||
} | |||
func ResultList(ctx *context.APIContext) { | |||
var ( | |||
err error | |||
) | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
parentDir := ctx.Query("parentDir") | |||
dirArray := strings.Split(parentDir, "/") | |||
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) | |||
if err != nil { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
models, err := storage.GetObsListObject(task.JobName, "result/", parentDir, versionName) | |||
if err != nil { | |||
log.Info("get TrainJobListModel failed:", err) | |||
ctx.ServerError("GetObsListObject:", err) | |||
@@ -146,7 +146,8 @@ func SaveModel(ctx *context.Context) { | |||
if !trainTaskCreate { | |||
if !ctx.Repo.CanWrite(models.UnitTypeModelManage) { | |||
ctx.ServerError("No right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
//ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||
ctx.JSON(403, ctx.Tr("repo.model_noright")) | |||
return | |||
} | |||
} | |||
@@ -209,20 +210,11 @@ func DeleteModel(ctx *context.Context) { | |||
}) | |||
} | |||
} | |||
func isCanDeleteOrDownload(ctx *context.Context, model *models.AiModelManage) bool { | |||
if ctx.User.IsAdmin || ctx.User.ID == model.UserId { | |||
return true | |||
} | |||
if ctx.Repo.IsOwner() { | |||
return true | |||
} | |||
return false | |||
} | |||
func deleteModelByID(ctx *context.Context, id string) error { | |||
log.Info("delete model start. id=" + id) | |||
model, err := models.QueryModelById(id) | |||
if !isCanDeleteOrDownload(ctx, model) { | |||
if !isCanDelete(ctx, model.UserId) { | |||
return errors.New(ctx.Tr("repo.model_noright")) | |||
} | |||
if err == nil { | |||
@@ -278,8 +270,8 @@ func DownloadMultiModelFile(ctx *context.Context) { | |||
ctx.ServerError("no such model:", err) | |||
return | |||
} | |||
if !isCanDeleteOrDownload(ctx, task) { | |||
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
if !isOper(ctx, task.UserId) { | |||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||
return | |||
} | |||
@@ -371,7 +363,16 @@ func DownloadSingleModelFile(ctx *context.Context) { | |||
parentDir := ctx.Query("parentDir") | |||
fileName := ctx.Query("fileName") | |||
path := Model_prefix + models.AttachmentRelativePath(id) + "/" + parentDir + fileName | |||
task, err := models.QueryModelById(id) | |||
if err != nil { | |||
log.Error("no such model!", err.Error()) | |||
ctx.ServerError("no such model:", err) | |||
return | |||
} | |||
if !isOper(ctx, task.UserId) { | |||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||
return | |||
} | |||
if setting.PROXYURL != "" { | |||
body, err := storage.ObsDownloadAFile(setting.Bucket, path) | |||
if err != nil { | |||
@@ -414,6 +415,8 @@ func ShowModelInfo(ctx *context.Context) { | |||
ctx.Data["ID"] = ctx.Query("ID") | |||
ctx.Data["name"] = ctx.Query("name") | |||
ctx.Data["isModelManage"] = true | |||
ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage) | |||
ctx.HTML(200, tplModelInfo) | |||
} | |||
@@ -426,6 +429,7 @@ func ShowSingleModel(ctx *context.Context) { | |||
userIds := make([]int64, len(models)) | |||
for i, model := range models { | |||
model.IsCanOper = isOper(ctx, model.UserId) | |||
model.IsCanDelete = isCanDelete(ctx, model.UserId) | |||
userIds[i] = model.UserId | |||
} | |||
userNameMap := queryUserName(userIds) | |||
@@ -468,6 +472,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) { | |||
userIds := make([]int64, len(aimodels)) | |||
for i, model := range aimodels { | |||
model.IsCanOper = isOper(ctx, model.UserId) | |||
model.IsCanDelete = isCanDelete(ctx, model.UserId) | |||
userIds[i] = model.UserId | |||
} | |||
userNameMap := queryUserName(userIds) | |||
@@ -487,8 +492,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) { | |||
} | |||
} | |||
func ShowModelTemplate(ctx *context.Context) { | |||
ctx.Data["isModelManage"] = true | |||
func SetModelCount(ctx *context.Context) { | |||
repoId := ctx.Repo.Repository.ID | |||
Type := -1 | |||
_, count, _ := models.QueryModel(&models.AiModelQueryOptions{ | |||
@@ -501,10 +505,15 @@ func ShowModelTemplate(ctx *context.Context) { | |||
New: MODEL_LATEST, | |||
}) | |||
ctx.Data["MODEL_COUNT"] = count | |||
} | |||
func ShowModelTemplate(ctx *context.Context) { | |||
ctx.Data["isModelManage"] = true | |||
repoId := ctx.Repo.Repository.ID | |||
SetModelCount(ctx) | |||
ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage) | |||
_, trainCount, _ := models.QueryModelTrainJobList(repoId) | |||
log.Info("query train count=" + fmt.Sprint(trainCount)) | |||
ctx.Data["TRAIN_COUNT"] = trainCount | |||
ctx.HTML(200, tplModelManageIndex) | |||
} | |||
@@ -520,11 +529,24 @@ func isQueryRight(ctx *context.Context) bool { | |||
} | |||
} | |||
func isCanDelete(ctx *context.Context, modelUserId int64) bool { | |||
if ctx.User == nil { | |||
return false | |||
} | |||
if ctx.User.IsAdmin || ctx.User.ID == modelUserId { | |||
return true | |||
} | |||
if ctx.Repo.IsOwner() { | |||
return true | |||
} | |||
return false | |||
} | |||
func isOper(ctx *context.Context, modelUserId int64) bool { | |||
if ctx.User == nil { | |||
return false | |||
} | |||
if ctx.User.IsAdmin || ctx.Repo.IsOwner() || ctx.User.ID == modelUserId { | |||
if ctx.User.IsAdmin || ctx.User.ID == modelUserId { | |||
return true | |||
} | |||
return false | |||
@@ -533,7 +555,7 @@ func isOper(ctx *context.Context, modelUserId int64) bool { | |||
func ShowModelPageInfo(ctx *context.Context) { | |||
log.Info("ShowModelInfo start.") | |||
if !isQueryRight(ctx) { | |||
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||
return | |||
} | |||
page := ctx.QueryInt("page") | |||
@@ -563,6 +585,7 @@ func ShowModelPageInfo(ctx *context.Context) { | |||
userIds := make([]int64, len(modelResult)) | |||
for i, model := range modelResult { | |||
model.IsCanOper = isOper(ctx, model.UserId) | |||
model.IsCanDelete = isCanDelete(ctx, model.UserId) | |||
userIds[i] = model.UserId | |||
} | |||
@@ -603,8 +626,9 @@ func ModifyModelInfo(ctx *context.Context) { | |||
ctx.ServerError("no such model:", err) | |||
return | |||
} | |||
if !isCanDeleteOrDownload(ctx, task) { | |||
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
if !isOper(ctx, task.UserId) { | |||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||
//ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
return | |||
} | |||
@@ -1,15 +1,18 @@ | |||
package repo | |||
import ( | |||
"archive/zip" | |||
"encoding/json" | |||
"errors" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"os" | |||
"path" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"unicode/utf8" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/auth" | |||
@@ -37,6 +40,10 @@ const ( | |||
tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new" | |||
tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show" | |||
tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new" | |||
tplModelArtsInferenceJobIndex base.TplName = "repo/modelarts/inferencejob/index" | |||
tplModelArtsInferenceJobNew base.TplName = "repo/modelarts/inferencejob/new" | |||
tplModelArtsInferenceJobShow base.TplName = "repo/modelarts/inferencejob/show" | |||
) | |||
func DebugJobIndex(ctx *context.Context) { | |||
@@ -49,12 +56,15 @@ func DebugJobIndex(ctx *context.Context) { | |||
page = 1 | |||
} | |||
debugType := modelarts.DebugType | |||
jobTypeNot := false | |||
if debugListType == models.GPUResource { | |||
debugType = models.TypeCloudBrainOne | |||
} else if debugListType == models.NPUResource { | |||
debugType = models.TypeCloudBrainTwo | |||
} | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeBenchmark), string(models.JobTypeSnn4imagenet), string(models.JobTypeBrainScore), string(models.JobTypeDebug)) | |||
ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
@@ -62,8 +72,8 @@ func DebugJobIndex(ctx *context.Context) { | |||
}, | |||
RepoID: repo.ID, | |||
Type: debugType, | |||
JobTypeNot: true, | |||
JobType: string(models.JobTypeTrain), | |||
JobTypeNot: jobTypeNot, | |||
JobTypes: jobTypes, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Get debugjob faild:", err) | |||
@@ -367,6 +377,8 @@ func TrainJobIndex(ctx *context.Context) { | |||
page = 1 | |||
} | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeTrain)) | |||
tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
@@ -375,7 +387,7 @@ func TrainJobIndex(ctx *context.Context) { | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobTypeNot: false, | |||
JobType: string(models.JobTypeTrain), | |||
JobTypes: jobTypes, | |||
IsLatestVersion: modelarts.IsLatestVersion, | |||
}) | |||
if err != nil { | |||
@@ -749,7 +761,7 @@ func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrai | |||
func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) { | |||
ctx.Data["PageIsTrainJob"] = true | |||
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(modelarts.TotalVersionCount) | |||
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
@@ -794,18 +806,11 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
return | |||
} | |||
// attach, err := models.GetAttachmentByUUID(uuid) | |||
// if err != nil { | |||
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) | |||
// return | |||
// } | |||
//todo: del the codeLocalPath | |||
// _, err := ioutil.ReadDir(codeLocalPath) | |||
// if err == nil { | |||
// os.RemoveAll(codeLocalPath) | |||
// } | |||
os.RemoveAll(codeLocalPath) | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
@@ -973,7 +978,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err) | |||
return | |||
} | |||
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(latestTask.TotalVersionCount + 1) | |||
VersionOutputPath := modelarts.GetOutputPathByCount(latestTask.TotalVersionCount + 1) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
@@ -1011,18 +1016,17 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
return | |||
} | |||
// attach, err := models.GetAttachmentByUUID(uuid) | |||
// if err != nil { | |||
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) | |||
// return | |||
// } | |||
//todo: del the codeLocalPath | |||
// _, err = ioutil.ReadDir(codeLocalPath) | |||
// if err == nil { | |||
// os.RemoveAll(codeLocalPath) | |||
// } | |||
os.RemoveAll(codeLocalPath) | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} else { | |||
log.Error("创建任务失败,原代码还未删除,请重试!: %s (%v)", repo.FullName(), err) | |||
versionErrorDataPrepare(ctx, form) | |||
ctx.RenderWithErr("创建任务失败,原代码还未删除,请重试!", tplModelArtsTrainJobVersionNew, &form) | |||
return | |||
} | |||
// os.RemoveAll(codeLocalPath) | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
@@ -1264,6 +1268,42 @@ func paramCheckCreateTrainJob(form auth.CreateModelArtsTrainJobForm) error { | |||
return nil | |||
} | |||
func paramCheckCreateInferenceJob(form auth.CreateModelArtsInferenceJobForm) error { | |||
if !strings.HasSuffix(form.BootFile, ".py") { | |||
log.Error("the boot file(%s) must be a python file", form.BootFile) | |||
return errors.New("启动文件必须是python文件") | |||
} | |||
if form.WorkServerNumber > 25 || form.WorkServerNumber < 1 { | |||
log.Error("the WorkServerNumber(%d) must be in (1,25)", form.WorkServerNumber) | |||
return errors.New("计算节点数必须在1-25之间") | |||
} | |||
if form.ModelName == "" { | |||
log.Error("the ModelName(%d) must not be nil", form.ModelName) | |||
return errors.New("模型名称不能为空") | |||
} | |||
if form.ModelVersion == "" { | |||
log.Error("the ModelVersion(%d) must not be nil", form.ModelVersion) | |||
return errors.New("模型版本不能为空") | |||
} | |||
if form.CkptName == "" { | |||
log.Error("the CkptName(%d) must not be nil", form.CkptName) | |||
return errors.New("权重文件不能为空") | |||
} | |||
if form.BranchName == "" { | |||
log.Error("the Branch(%d) must not be nil", form.BranchName) | |||
return errors.New("分支名不能为空") | |||
} | |||
if utf8.RuneCountInString(form.Description) > 255 { | |||
log.Error("the Description length(%d) must not more than 255", form.Description) | |||
return errors.New("描述字符不能超过255个字符") | |||
} | |||
return nil | |||
} | |||
func TrainJobShow(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
var jobID = ctx.Params(":jobid") | |||
@@ -1273,6 +1313,9 @@ func TrainJobShow(ctx *context.Context) { | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeTrain)) | |||
VersionListTasks, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
@@ -1280,7 +1323,7 @@ func TrainJobShow(ctx *context.Context) { | |||
}, | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobType: string(models.JobTypeTrain), | |||
JobTypes: jobTypes, | |||
JobID: jobID, | |||
}) | |||
@@ -1392,10 +1435,12 @@ func TrainJobDel(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
repo := ctx.Repo.Repository | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeTrain)) | |||
VersionListTasks, _, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobType: string(models.JobTypeTrain), | |||
JobTypes: jobTypes, | |||
JobID: jobID, | |||
}) | |||
if err != nil { | |||
@@ -1518,6 +1563,427 @@ func getConfigList(perPage, page int, sortBy, order, searchContent, configType s | |||
return list, nil | |||
} | |||
func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) { | |||
ctx.Data["PageIsTrainJob"] = true | |||
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
workServerNumber := form.WorkServerNumber | |||
engineID := form.EngineID | |||
bootFile := form.BootFile | |||
flavorCode := form.Flavor | |||
params := form.Params | |||
poolID := form.PoolID | |||
repo := ctx.Repo.Repository | |||
codeLocalPath := setting.JobPath + jobName + modelarts.CodePath | |||
codeObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.CodePath | |||
resultObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.ResultPath + VersionOutputPath + "/" | |||
logObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.LogPath + VersionOutputPath + "/" | |||
dataPath := "/" + setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + uuid + "/" | |||
branch_name := form.BranchName | |||
FlavorName := form.FlavorName | |||
EngineName := form.EngineName | |||
LabelName := form.LabelName | |||
isLatestVersion := modelarts.IsLatestVersion | |||
VersionCount := modelarts.VersionCount | |||
trainUrl := form.TrainUrl | |||
modelName := form.ModelName | |||
modelVersion := form.ModelVersion | |||
ckptName := form.CkptName | |||
ckptUrl := form.TrainUrl + form.CkptName | |||
if err := paramCheckCreateInferenceJob(form); err != nil { | |||
log.Error("paramCheckCreateInferenceJob failed:(%v)", err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
count, err := models.GetCloudbrainInferenceJobCountByUserID(ctx.User.ID) | |||
if err != nil { | |||
log.Error("GetCloudbrainInferenceJobCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("system error", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} else { | |||
if count >= 1 { | |||
log.Error("the user already has running or waiting inference task", ctx.Data["MsgID"]) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("you have already a running or waiting inference task, can not create more", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
} | |||
//todo: del the codeLocalPath | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
if err := git.Clone(repo.RepoPath(), codeLocalPath, git.CloneRepoOptions{ | |||
Branch: branch_name, | |||
}); err != nil { | |||
log.Error("创建任务失败,服务器超时!: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("创建任务失败,服务器超时!", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
//todo: upload code (send to file_server todo this work?) | |||
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.ResultPath + VersionOutputPath + "/"); err != nil { | |||
log.Error("Failed to obsMkdir_result: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to obsMkdir_result", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil { | |||
log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil { | |||
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
//todo: del local code? | |||
var parameters models.Parameters | |||
param := make([]models.Parameter, 0) | |||
param = append(param, models.Parameter{ | |||
Label: modelarts.ResultUrl, | |||
Value: "s3:/" + resultObsPath, | |||
}, models.Parameter{ | |||
Label: modelarts.CkptUrl, | |||
Value: "s3:/" + ckptUrl, | |||
}) | |||
if len(params) != 0 { | |||
err := json.Unmarshal([]byte(params), ¶meters) | |||
if err != nil { | |||
log.Error("Failed to Unmarshal params: %s (%v)", params, err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("运行参数错误", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
for _, parameter := range parameters.Parameter { | |||
if parameter.Label != modelarts.TrainUrl && parameter.Label != modelarts.DataUrl { | |||
param = append(param, models.Parameter{ | |||
Label: parameter.Label, | |||
Value: parameter.Value, | |||
}) | |||
} | |||
} | |||
} | |||
req := &modelarts.GenerateInferenceJobReq{ | |||
JobName: jobName, | |||
DataUrl: dataPath, | |||
Description: description, | |||
CodeObsPath: codeObsPath, | |||
BootFileUrl: codeObsPath + bootFile, | |||
BootFile: bootFile, | |||
TrainUrl: trainUrl, | |||
FlavorCode: flavorCode, | |||
WorkServerNumber: workServerNumber, | |||
EngineID: int64(engineID), | |||
LogUrl: logObsPath, | |||
PoolID: poolID, | |||
Uuid: uuid, | |||
Parameters: param, //modelarts训练时用到 | |||
CommitID: commitID, | |||
BranchName: branch_name, | |||
Params: form.Params, | |||
FlavorName: FlavorName, | |||
EngineName: EngineName, | |||
LabelName: LabelName, | |||
IsLatestVersion: isLatestVersion, | |||
VersionCount: VersionCount, | |||
TotalVersionCount: modelarts.TotalVersionCount, | |||
ModelName: modelName, | |||
ModelVersion: modelVersion, | |||
CkptName: ckptName, | |||
ResultUrl: resultObsPath, | |||
} | |||
//将params转换Parameters.Parameter,出错时返回给前端 | |||
// var Parameters modelarts.Parameters | |||
// if err := json.Unmarshal([]byte(params), &Parameters); err != nil { | |||
// ctx.ServerError("json.Unmarshal failed:", err) | |||
// return | |||
// } | |||
err = modelarts.GenerateInferenceJob(ctx, req) | |||
if err != nil { | |||
log.Error("GenerateTrainJob failed:%v", err.Error()) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/inference-job") | |||
} | |||
func InferenceJobIndex(ctx *context.Context) { | |||
MustEnableModelArts(ctx) | |||
repo := ctx.Repo.Repository | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
var jobTypes []string | |||
jobTypes = append(jobTypes, string(models.JobTypeInference)) | |||
tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
PageSize: setting.UI.IssuePagingNum, | |||
}, | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobTypes: jobTypes, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Cloudbrain", err) | |||
return | |||
} | |||
for i, task := range tasks { | |||
tasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) | |||
tasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain) | |||
tasks[i].ComputeResource = models.NPUResource | |||
} | |||
repoId := ctx.Repo.Repository.ID | |||
Type := -1 | |||
_, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: 1, | |||
PageSize: 2, | |||
}, | |||
RepoID: repoId, | |||
Type: Type, | |||
New: MODEL_LATEST, | |||
}) | |||
ctx.Data["MODEL_COUNT"] = model_count | |||
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
ctx.Data["Page"] = pager | |||
ctx.Data["PageIsCloudBrain"] = true | |||
ctx.Data["Tasks"] = tasks | |||
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) | |||
ctx.Data["RepoIsEmpty"] = repo.IsEmpty | |||
ctx.HTML(200, tplModelArtsInferenceJobIndex) | |||
} | |||
func InferenceJobNew(ctx *context.Context) { | |||
err := inferenceJobNewDataPrepare(ctx) | |||
if err != nil { | |||
ctx.ServerError("get new inference-job info failed", err) | |||
return | |||
} | |||
ctx.HTML(200, tplModelArtsInferenceJobNew) | |||
} | |||
func inferenceJobNewDataPrepare(ctx *context.Context) error { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
t := time.Now() | |||
var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||
ctx.Data["job_name"] = jobName | |||
attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) | |||
if err != nil { | |||
ctx.ServerError("GetAllUserAttachments failed:", err) | |||
return err | |||
} | |||
ctx.Data["attachments"] = attachs | |||
var resourcePools modelarts.ResourcePool | |||
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["resource_pools"] = resourcePools.Info | |||
var engines modelarts.Engine | |||
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engines"] = engines.Info | |||
var versionInfos modelarts.VersionInfo | |||
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engine_versions"] = versionInfos.Version | |||
var flavorInfos modelarts.Flavor | |||
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["flavor_infos"] = flavorInfos.Info | |||
ctx.Data["params"] = "" | |||
ctx.Data["branchName"] = ctx.Repo.BranchName | |||
configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) | |||
if err != nil { | |||
ctx.ServerError("getConfigList failed:", err) | |||
return err | |||
} | |||
ctx.Data["config_list"] = configList.ParaConfigs | |||
repoId := ctx.Repo.Repository.ID | |||
Type := -1 | |||
_, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: 1, | |||
PageSize: 2, | |||
}, | |||
RepoID: repoId, | |||
Type: Type, | |||
New: MODEL_LATEST, | |||
}) | |||
ctx.Data["MODEL_COUNT"] = model_count | |||
return nil | |||
} | |||
func inferenceJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) error { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
t := time.Now() | |||
var jobName = "inference" + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||
ctx.Data["job_name"] = jobName | |||
attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) | |||
if err != nil { | |||
ctx.ServerError("GetAllUserAttachments failed:", err) | |||
return err | |||
} | |||
ctx.Data["attachments"] = attachs | |||
var resourcePools modelarts.ResourcePool | |||
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["resource_pools"] = resourcePools.Info | |||
var engines modelarts.Engine | |||
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engines"] = engines.Info | |||
var versionInfos modelarts.VersionInfo | |||
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engine_versions"] = versionInfos.Version | |||
var flavorInfos modelarts.Flavor | |||
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["flavor_infos"] = flavorInfos.Info | |||
configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) | |||
if err != nil { | |||
ctx.ServerError("getConfigList failed:", err) | |||
return err | |||
} | |||
var Parameters modelarts.Parameters | |||
if err = json.Unmarshal([]byte(form.Params), &Parameters); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["params"] = Parameters.Parameter | |||
ctx.Data["config_list"] = configList.ParaConfigs | |||
ctx.Data["bootFile"] = form.BootFile | |||
ctx.Data["uuid"] = form.Attachment | |||
ctx.Data["branch_name"] = form.BranchName | |||
ctx.Data["model_name"] = form.ModelName | |||
ctx.Data["model_version"] = form.ModelVersion | |||
ctx.Data["ckpt_name"] = form.CkptName | |||
ctx.Data["train_url"] = form.TrainUrl | |||
return nil | |||
} | |||
func InferenceJobShow(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
var jobID = ctx.Params(":jobid") | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
log.Error("GetInferenceTask(%s) failed:%v", jobID, err.Error()) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobShow, nil) | |||
return | |||
} | |||
//设置权限 | |||
canNewJob, err := canUserCreateTrainJobVersion(ctx, task.UserID) | |||
if err != nil { | |||
ctx.ServerError("canNewJob failed", err) | |||
return | |||
} | |||
ctx.Data["canNewJob"] = canNewJob | |||
//将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | |||
var parameters models.Parameters | |||
err = json.Unmarshal([]byte(task.Parameters), ¶meters) | |||
if err != nil { | |||
log.Error("Failed to Unmarshal Parameters: %s (%v)", task.Parameters, err) | |||
trainJobNewDataPrepare(ctx) | |||
return | |||
} | |||
if len(parameters.Parameter) > 0 { | |||
paramTemp := "" | |||
for _, Parameter := range parameters.Parameter { | |||
param := Parameter.Label + " = " + Parameter.Value + "; " | |||
paramTemp = paramTemp + param | |||
} | |||
task.Parameters = paramTemp[:len(paramTemp)-2] | |||
} else { | |||
task.Parameters = "" | |||
} | |||
LabelName := strings.Fields(task.LabelName) | |||
ctx.Data["labelName"] = LabelName | |||
ctx.Data["jobID"] = jobID | |||
ctx.Data["jobName"] = task.JobName | |||
ctx.Data["task"] = task | |||
tempUids := []int64{} | |||
tempUids = append(tempUids, task.UserID) | |||
JobCreater, err := models.GetUserNamesByIDs(tempUids) | |||
if err != nil { | |||
log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err) | |||
} | |||
ctx.Data["userName"] = JobCreater[0] | |||
ctx.HTML(http.StatusOK, tplModelArtsInferenceJobShow) | |||
} | |||
func ModelDownload(ctx *context.Context) { | |||
var ( | |||
err error | |||
@@ -1546,6 +2012,31 @@ func ModelDownload(ctx *context.Context) { | |||
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) | |||
} | |||
func ResultDownload(ctx *context.Context) { | |||
var ( | |||
err error | |||
) | |||
var jobID = ctx.Params(":jobid") | |||
versionName := ctx.Query("version_name") | |||
parentDir := ctx.Query("parent_dir") | |||
fileName := ctx.Query("file_name") | |||
log.Info("DownloadResult start.") | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.Data["error"] = err.Error() | |||
} | |||
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName, parentDir, fileName), "/") | |||
log.Info("Download path is:%s", path) | |||
url, err := storage.GetObsCreateSignedUrlByBucketAndKey(setting.Bucket, path) | |||
if err != nil { | |||
log.Error("GetObsCreateSignedUrl failed: %v", err.Error(), ctx.Data["msgID"]) | |||
ctx.ServerError("GetObsCreateSignedUrl", err) | |||
return | |||
} | |||
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) | |||
} | |||
func DeleteJobStorage(jobName string) error { | |||
//delete local | |||
localJobPath := setting.JobPath + jobName | |||
@@ -1563,3 +2054,82 @@ func DeleteJobStorage(jobName string) error { | |||
return nil | |||
} | |||
func DownloadMultiResultFile(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) | |||
if err != nil { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
// if !isCanDeleteOrDownload(ctx, task) { | |||
// ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
// return | |||
// } | |||
// path := Model_prefix + models.AttachmentRelativePath(id) + "/" | |||
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName), "/") + "/" | |||
allFile, err := storage.GetAllObjectByBucketAndPrefix(setting.Bucket, path) | |||
if err == nil { | |||
//count++ | |||
// models.ModifyModelDownloadCount(id) | |||
returnFileName := task.JobName + ".zip" | |||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+returnFileName) | |||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream") | |||
w := zip.NewWriter(ctx.Resp) | |||
defer w.Close() | |||
for _, oneFile := range allFile { | |||
if oneFile.IsDir { | |||
log.Info("zip dir name:" + oneFile.FileName) | |||
} else { | |||
log.Info("zip file name:" + oneFile.FileName) | |||
fDest, err := w.Create(oneFile.FileName) | |||
if err != nil { | |||
log.Info("create zip entry error, download file failed: %s\n", err.Error()) | |||
ctx.ServerError("download file failed:", err) | |||
return | |||
} | |||
body, err := storage.ObsDownloadAFile(setting.Bucket, path+oneFile.FileName) | |||
if err != nil { | |||
log.Info("download file failed: %s\n", err.Error()) | |||
ctx.ServerError("download file failed:", err) | |||
return | |||
} else { | |||
defer body.Close() | |||
p := make([]byte, 1024) | |||
var readErr error | |||
var readCount int | |||
// 读取对象内容 | |||
for { | |||
readCount, readErr = body.Read(p) | |||
if readCount > 0 { | |||
fDest.Write(p[:readCount]) | |||
} | |||
if readErr != nil { | |||
break | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
log.Info("error,msg=" + err.Error()) | |||
ctx.ServerError("no file to download.", err) | |||
} | |||
} | |||
func SetJobCount(ctx *context.Context) { | |||
repoId := ctx.Repo.Repository.ID | |||
_, jobCount, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
RepoID: repoId, | |||
Type: modelarts.DebugType, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Get job faild:", err) | |||
return | |||
} | |||
ctx.Data["jobCount"] = jobCount | |||
} |
@@ -50,6 +50,8 @@ func Settings(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
ctx.Data["PageIsSettingsOptions"] = true | |||
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate | |||
SetModelCount(ctx) | |||
SetJobCount(ctx) | |||
ctx.HTML(200, tplSettingsOptions) | |||
} | |||
@@ -57,7 +59,8 @@ func Settings(ctx *context.Context) { | |||
func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||
ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
ctx.Data["PageIsSettingsOptions"] = true | |||
SetModelCount(ctx) | |||
SetJobCount(ctx) | |||
repo := ctx.Repo.Repository | |||
switch ctx.Query("action") { | |||
@@ -1033,6 +1033,17 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) | |||
}) | |||
m.Group("/inference-job", func() { | |||
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobIndex) | |||
m.Group("/:jobid", func() { | |||
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobShow) | |||
m.Get("/result_download", cloudbrain.AdminOrJobCreaterRight, repo.ResultDownload) | |||
m.Get("/downloadall", repo.DownloadMultiResultFile) | |||
}) | |||
m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew) | |||
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) | |||
}) | |||
}, context.RepoRef()) | |||
m.Group("/blockchain", func() { | |||
@@ -147,7 +147,7 @@ | |||
</div> | |||
<div class="inline required field"> | |||
<label>任务名称</label> | |||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254"> | |||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" onkeyup="this.value=this.value.replace(/[, ]/g,'')"> | |||
</div> | |||
<div class="inline required field" style="{{if ((.is_benchmark_enabled) or (.is_snn4imagenet_enabled) or (.is_brainscore_enabled))}}display:block;{{else}}display:none;{{end}}"> | |||
@@ -192,7 +192,7 @@ | |||
<div class="inline required field" style="position: relative;"> | |||
<label>镜像</label> | |||
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="254"> | |||
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="255"> | |||
<i class="times circle outline icon icons" style="visibility: hidden;" onclick="clearValue()"></i> | |||
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image"> | |||
{{range .images}} | |||
@@ -225,27 +225,27 @@ | |||
<div class="inline required field"> | |||
<label>数据集存放路径</label> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>模型存放路径</label> | |||
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>代码存放路径</label> | |||
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field cloudbrain_benchmark"> | |||
<label>benchmark脚本存放路径</label> | |||
<input name="benchmark_path" id="cloudbrain_benchmark_path" value="{{.benchmark_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="benchmark_path" id="cloudbrain_benchmark_path" value="{{.benchmark_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field cloudbrain_snn4imagenet"> | |||
<label>snn4imagenet脚本存放路径</label> | |||
<input name="snn4imagenet_path" id="cloudbrain_snn4imagenet_path" value="{{.snn4imagenet_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="snn4imagenet_path" id="cloudbrain_snn4imagenet_path" value="{{.snn4imagenet_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field cloudbrain_brainscore"> | |||
<label>brainscore脚本存放路径</label> | |||
<input name="brainscore_path" id="cloudbrain_brainscore_path" value="{{.brainscore_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="brainscore_path" id="cloudbrain_brainscore_path" value="{{.brainscore_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field" hidden> | |||
<label>启动命令</label> | |||
@@ -54,7 +54,7 @@ | |||
</div> | |||
<div class="inline field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> | |||
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea> | |||
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea> | |||
</div> | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.template"}}</label> | |||
@@ -57,7 +57,7 @@ | |||
<div class="ui grid form segment success {{if not .Error}}hide{{end}}" id="dataset-content-edit"> | |||
<label class="d-block">{{.i18n.Tr "dataset.title"}}</label> | |||
<div class="sixteen wide column"> | |||
<input name="title" placeholder='{{.i18n.Tr "dataset.title"}}' value="{{.dataset.Title}}" autofocus required maxlength="254"> | |||
<input name="title" placeholder='{{.i18n.Tr "dataset.title"}}' value="{{.dataset.Title}}" autofocus required maxlength="255"> | |||
</div> | |||
<label class="d-block">{{.i18n.Tr "dataset.description"}}</label> | |||
<div class="sixteen wide column"> | |||
@@ -217,6 +217,7 @@ | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="active item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
@@ -430,12 +431,12 @@ | |||
<div class="inline required field dis"> | |||
<label>镜像标签:</label> | |||
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="254" style="width:75%"> | |||
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="255" style="width:75%"> | |||
</div> | |||
<div class="inline field"> | |||
<label class="label_after">镜像描述:</label> | |||
<textarea name="description" maxlength="254" rows="8" style="width:75%;margin-left: 0.2em;"></textarea> | |||
<textarea name="description" maxlength="255" rows="8" style="width:75%;margin-left: 0.2em;"></textarea> | |||
</div> | |||
<div class="ui divider"></div> | |||
<div class="inline field"> | |||
@@ -488,6 +489,7 @@ | |||
<script> | |||
// 调试和评分新开窗口 | |||
console.log({{.Tasks}}) | |||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||
let url={{.RepoLink}} | |||
let getParam=getQueryVariable('debugListType') | |||
@@ -606,7 +608,7 @@ | |||
const jobID = job.dataset.jobid; | |||
const repoPath = job.dataset.repopath; | |||
const computeResource = job.dataset.resource | |||
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED'] | |||
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED','UNAVAILABLE','DELETED','RESIZE_FAILED'] | |||
if (initArray.includes(job.textContent.trim())) { | |||
return | |||
@@ -15,7 +15,7 @@ | |||
<div class="ui segment content"> | |||
<div class="field"> | |||
<!-- --> | |||
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="254"> | |||
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255"> | |||
{{if .PageIsComparePull}} | |||
<div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div> | |||
{{end}} | |||
@@ -3,7 +3,7 @@ | |||
<h1 class="twelve wide column"> | |||
<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{RenderEmoji .Issue.Title}}</span> | |||
<div id="edit-title-input" class="ui input" style="display: none"> | |||
<input value="{{.Issue.Title}}" maxlength="254"> | |||
<input value="{{.Issue.Title}}" maxlength="255"> | |||
</div> | |||
</h1> | |||
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}} | |||
@@ -122,7 +122,7 @@ | |||
</div> | |||
<div class="inline field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> | |||
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea> | |||
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea> | |||
</div> | |||
<div class="inline field"> | |||
@@ -0,0 +1,337 @@ | |||
<!-- 头部导航栏 --> | |||
{{template "base/head" .}} | |||
<style> | |||
.fontsize14{ | |||
font-size: 14px; | |||
} | |||
.padding0{ | |||
padding: 0 !important; | |||
} | |||
</style> | |||
<!-- 弹窗 --> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<!-- 提示框 --> | |||
<div class="alert"></div> | |||
<div class="repository release dataset-list view"> | |||
{{template "repo/header" .}} | |||
<!-- 列表容器 --> | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<div class="ui two column stackable grid "> | |||
<div class="column"> | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="active item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
{{if .Permission.CanWrite $.UnitTypeCloudBrain}} | |||
<a class="ui green button" href="{{.RepoLink}}/modelarts/inference-job/create">{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a> | |||
{{else}} | |||
<a class="ui disabled button" >{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
{{if eq 0 (len .Tasks)}} | |||
<div class="ui placeholder segment bgtask-none"> | |||
<div class="ui icon header bgtask-header-pic"></div> | |||
<div class="bgtask-content-header">未创建过推理任务</div> | |||
<div class="bgtask-content"> | |||
{{if $.RepoIsEmpty}} | |||
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | |||
{{end}} | |||
{{if eq 0 $.MODEL_COUNT}} | |||
<div class="bgtask-content-txt">模型文件:您还没有模型文件,请先通过<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>产生并 <a href="{{.RepoLink}}/modelmanage/show_model">导出模型</a> ;</div> | |||
{{end}} | |||
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | |||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
</div> | |||
</div> | |||
{{else}} | |||
<!-- 中下列表展示区 --> | |||
<div class="ui grid"> | |||
<div class="row"> | |||
<div class="ui sixteen wide column"> | |||
<!-- 任务展示 --> | |||
<div class="dataset list"> | |||
<!-- 表头 --> | |||
<div class="ui grid stackable" style="background: #f0f0f0;;"> | |||
<div class="row"> | |||
<div class="three wide column padding0"> | |||
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span> | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<span style="margin:0 6px">{{$.i18n.Tr "repo.modelarts.infer_job.model_version"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.status"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_status_runtime"}}</span> | |||
</div> | |||
<!-- <div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span> | |||
</div> --> | |||
<div class="one wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span> | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
{{range .Tasks}} | |||
<div class="ui grid stackable item"> | |||
<div class="row"> | |||
<!-- 任务名 --> | |||
<div class="three wide column padding0"> | |||
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;"> | |||
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span> | |||
</a> | |||
</div> | |||
<!-- 模型版本 --> | |||
<!-- href="{{$.RepoLink}}/modelmanage/show_model_info?name={{.ModelName}}" --> | |||
<div class="three wide column text center padding0"> | |||
<a id="{{.JobName}}" href="javascript:void(0);" data-variation="inverted" data-position="top center" data-content="{{$.i18n.Tr "repo.modelarts.infer_job.tooltip"}}" onclick="getModelInfo({{.ModelName}},{{.ModelVersion}},{{.JobName}})">{{.ModelName}} </a> <span style="font-size: 12px;">{{.ModelVersion}} </span> | |||
</div> | |||
<!-- 任务状态 --> | |||
<div class="two wide column text center padding0" > | |||
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}"> | |||
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span> | |||
</span> | |||
</div> | |||
<!-- 任务创建时间 --> | |||
<div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> | |||
</div> | |||
<!-- 任务运行时间 --> | |||
<div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{.TrainJobDuration}}</span> | |||
</div> | |||
<!-- 计算资源 --> | |||
<!-- <div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;">{{.ComputeResource}}</span> | |||
</div> --> | |||
<!-- 创建者 --> | |||
<div class="one wide column text center padding0"> | |||
{{if .User.Name}} | |||
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a> | |||
{{else}} | |||
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a> | |||
{{end}} | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<!-- 停止任务 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
<!-- 下载 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanModify}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-download-{{.JobID}}" href="{{$.RepoLink}}/modelarts/inference-job/{{.JobID}}/downloadall?version_name={{.VersionName}}" class="ui basic blue button" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.model_download"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.model_download"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
<!-- 删除任务 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this,{{.VersionName}},{{.JobID}})" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.delete"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.delete"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} {{template "base/paginate" .}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div id="deletemodel"> | |||
<div class="ui basic modal"> | |||
<div class="ui icon header"> | |||
<i class="trash icon"></i> 删除任务 | |||
</div> | |||
<div class="content"> | |||
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p> | |||
</div> | |||
<div class="actions"> | |||
<div class="ui red basic inverted cancel button"> | |||
<i class="remove icon"></i> 取消操作 | |||
</div> | |||
<div class="ui green basic inverted ok button"> | |||
<i class="checkmark icon"></i> 确定操作 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
// 加载任务状态 | |||
var timeid = window.setInterval(loadJobStatus, 15000); | |||
$(document).ready(loadJobStatus); | |||
function loadJobStatus() { | |||
$(".job-status").each((index, job) => { | |||
const jobID = job.dataset.jobid | |||
const repoPath = job.dataset.repopath | |||
const versionname = job.dataset.version | |||
const status_text = $(`#${jobID}-text`).text() | |||
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED'] | |||
if(finalState.includes(status_text)){ | |||
return | |||
} | |||
$.get(`/api/v1/repos/${repoPath}/modelarts/inference-job/${jobID}?version_name=${versionname}`, (data) => { | |||
const jobID = data.JobID | |||
const status = data.JobStatus | |||
const duration = data.JobDuration | |||
$('#duration-'+jobID).text(duration) | |||
if (status != job.textContent.trim()) { | |||
$('#' + jobID+'-icon').removeClass().addClass(status) | |||
$('#' + jobID+ '-text').text(status) | |||
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled') | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
}); | |||
}; | |||
function getModelInfo(ID,version,JobName){ | |||
$.get("{{$.RepoLink}}/modelmanage/show_model_info_api?name="+ID,(data)=>{ | |||
if(data.length===0){ | |||
$(`#${JobName}`).popup('toggle') | |||
}else{ | |||
let versionData = data.filter((item)=>{ | |||
return item.Version === version | |||
}) | |||
if(versionData.length==0){ | |||
$(`#${JobName}`).popup('toggle') | |||
} | |||
else{ | |||
location.href = "{{$.RepoLink}}/modelmanage/show_model_info?name="+ID | |||
} | |||
} | |||
}) | |||
} | |||
function deleteVersion(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/del_version' | |||
$.post(url,{version_name:version_name},(data)=>{ | |||
if(data.StatusOK===0){ | |||
location.reload() | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function stopVersion(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/stop_version' | |||
$.post(url,{version_name:version_name},(data)=>{ | |||
if(data.StatusOK===0){ | |||
$('#'+version_name+'-stop').removeClass('blue') | |||
$('#'+version_name+'-stop').addClass('disabled') | |||
refreshStatus(version_name,jobID) | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function refreshStatus(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'?version_name='+version_name | |||
$.get(url,(data)=>{ | |||
$(`#${jobID}-icon`).attr("class",data.JobStatus) | |||
// detail status and duration | |||
$(`#${jobID}-text`).text(data.JobStatus) | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function assertDelete(obj,version_name,jobID) { | |||
if (obj.style.color == "rgb(204, 204, 204)") { | |||
return | |||
} else { | |||
// var delId = obj.parentNode.id | |||
flag = 1; | |||
$('.ui.basic.modal') | |||
.modal({ | |||
onDeny: function() { | |||
flag = false | |||
}, | |||
onApprove: function() { | |||
// document.getElementById(delId).submit() | |||
deleteVersion(version_name,jobID) | |||
flag = true | |||
}, | |||
onHidden: function() { | |||
if (flag == false) { | |||
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut(); | |||
} | |||
} | |||
}) | |||
.modal('show') | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,477 @@ | |||
{{template "base/head" .}} | |||
<style> | |||
.unite{ | |||
font-family: SourceHanSansSC-medium !important; | |||
color: rgba(16, 16, 16, 100) !important; | |||
} | |||
.title{ | |||
font-size: 16px !important; | |||
padding-left: 3rem !important; | |||
} | |||
.min_title{ | |||
font-size: 14px !important; | |||
padding-left: 6rem !important; | |||
margin-bottom: 2rem !important; | |||
} | |||
.width80{ | |||
width: 80.7% !important; | |||
} | |||
.width84{ | |||
width: 84% !important; | |||
margin-left: 5.1rem !important; | |||
} | |||
.width35{ | |||
width: 35.5% !important; | |||
} | |||
.nowrap { | |||
white-space: nowrap !important; | |||
} | |||
</style> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<h4 class="ui top attached header"> | |||
{{.i18n.Tr "repo.modelarts.train_job.new_infer"}} | |||
</h4> | |||
<div class="ui attached segment"> | |||
<!-- equal width --> | |||
<form class="ui form" action="{{.Link}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<input type="hidden" name="action" value="update"> | |||
<input type="hidden" id="ai_engine_name" name="engine_names" value=""> | |||
<input type="hidden" id="ai_flaver_name" name="flaver_names" value=""> | |||
{{if $.model_version}} | |||
<input type="hidden" id="ai_model_version" name="model_version" value="{{$.model_version}}"> | |||
{{else}} | |||
<input type="hidden" id="ai_model_version" name="model_version" value=""> | |||
{{end}} | |||
{{if $.label_names}} | |||
<input type="hidden" id="ai_model_label" name="label_names" value="{{$.label_names}}"> | |||
{{else}} | |||
<input type="hidden" id="ai_model_label" name="label_names" value=""> | |||
{{end}} | |||
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> | |||
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" onkeyup="this.value=this.value.replace(/[, ]/g,'')" tabindex="3" autofocus required maxlength="64"> | |||
<span class="tooltips" style="display: block;">请输入字母、数字、_和-,最长64个字符,且不能以中划线(-)结尾。</span> | |||
</div> | |||
<div class="unite min_title inline field"> | |||
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label> | |||
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea> | |||
</div> | |||
<div class="ui divider"></div> | |||
<!-- 模型相关配置 --> | |||
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}:</h4> | |||
<div class="required unite inline min_title fields" style="width: 91.8%;"> | |||
<div class="required eight wide field"> | |||
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.infer_job.select_model"}}</label> | |||
<div class="ui fluid search selection dropdown loading " id="select_model"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="model_name" value="{{$.model_name}}" required> | |||
<div class="text">{{$.model_name}}</div> | |||
{{else}} | |||
<input type="hidden" name="model_name" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_name"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="three wide field"> | |||
<div class="ui fluid search selection dropdown" id="select_model_version"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="train_url" value="{{$.train_url}}" required> | |||
<div class="text">{{$.model_version}}</div> | |||
{{else}} | |||
<input type="hidden" name="train_url" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_name_version"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="five wide field"> | |||
<div class="ui fluid search selection dropdown" id="select_model_checkpoint"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="ckpt_name" value="{{$.ckpt_name}}" required> | |||
<div class="text">{{$.ckpt_name}}</div> | |||
{{else}} | |||
<input type="hidden" name="ckpt_name" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_checkpoint"> | |||
</div> | |||
</div> | |||
</div> | |||
<span > | |||
<i class="question circle icon" data-content="模型文件位置存储在环境变量ckpt_url中。" data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
</div> | |||
<!-- AI引擎 --> | |||
<div class="required unite inline min_title fields" style="width: 90%;"> | |||
<div class="required eight wide field"> | |||
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}</label> | |||
<select class="ui fluid selection search dropdown" id="trainjob_engines"> | |||
{{range .engines}} | |||
<option value="{{.Value}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<div class="eight wide field" id="engine_name"> | |||
<select class="ui fluid selection dropdown nowrap" id="trainjob_engine_versions" name="engine_id" style="white-space: nowrap;"> | |||
{{range .engine_versions}} | |||
<option name="engine_id" value="{{.ID}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
</div> | |||
<!-- 代码分支 --> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.code_version"}}</label> | |||
<select class="ui dropdown width35" id="code_version" name="branch_name"> | |||
{{if .branch_name}} | |||
<option name="branch_name" value="{{.branch_name}}">{{.branch_name}}</option> | |||
{{range $k, $v :=.Branches}} | |||
{{ if ne $v $.branch_name }} | |||
<option name="branch_name" value="{{$v}}">{{$v}}</option> | |||
{{end}} | |||
{{end}} | |||
{{else}} | |||
<option name="branch_name" value="{{.branchName}}">{{.branchName}}</option> | |||
{{range $k, $v :=.Branches}} | |||
{{ if ne $v $.branchName }} | |||
<option name="branch_name" value="{{$v}}">{{$v}}</option> | |||
{{end}} | |||
{{end}} | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 数据集 --> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label> | |||
<select class="ui dropdown width35" id="trainjob_datasets" name="attachment" placeholder="选择数据集" required> | |||
{{if $.uuid}} | |||
<option name="attachment" value="{{$.uuid}}">{{$.datasetName}}</option> | |||
{{end}} | |||
{{range .attachments}} | |||
<option value="">选择数据集</option> | |||
<option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option> | |||
{{end}} | |||
</select> | |||
<span> | |||
<i class="question circle icon" data-content="数据集位置存储在环境变量data_url中。" data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
</div> | |||
<!-- 启动文件 --> | |||
<div class="inline unite min_title field required"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> | |||
{{if .bootFile}} | |||
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="255" > | |||
{{else}} | |||
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" > | |||
{{end}} | |||
<span > | |||
<i class="question circle icon" data-content={{.i18n.Tr "repo.modelarts.infer_job.boot_file_helper"}} data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
<a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a> | |||
</div> | |||
<!-- 运行参数 --> | |||
<div class="inline unite min_title field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label> | |||
<span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span> | |||
<input id="store_run_para" type="hidden" name="run_para_list"> | |||
<div class="dynamic field" style="margin-top: 1rem;"> | |||
{{if ne 0 (len .params)}} | |||
{{range $k ,$v := .params}} | |||
<div class="two fields width84" id="para{{$k}}"> | |||
<div class="field"> | |||
<input type="text" name="shipping_first-name" value={{$v.Label}} required> | |||
</div> | |||
<div class="field"> | |||
<input type="text" name="shipping_last-name" value={{$v.Value}} required> | |||
</div> | |||
<span> | |||
<i class="trash icon"></i> | |||
</span> | |||
</div> | |||
{{end}} | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="required field " style="display: none;"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label> | |||
<select class="ui dropdown" id="trainjob_resource_pool" style='width:385px' name="pool_id"> | |||
{{range .resource_pools}} | |||
<option value="{{.ID}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 规格 --> | |||
<div class="required unite min_title inline field" id="flaver_name"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
<select class="ui dropdown width80" id="trainjob-flavor" name="flavor"> | |||
{{range .flavor_infos}} | |||
<option name="flavor" value="{{.Code}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 计算节点 --> | |||
<div class="inline required unite min_title field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> | |||
<div class="ui labeled input" style="width: 5%;"> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="1" readonly> | |||
</div> | |||
<span class="tooltips" style="display: block;">推理输出路径存储在环境变量result_url中。</span> | |||
</div> | |||
<!-- 表单操作 --> | |||
<div class="inline unite min_title field"> | |||
<button class="ui create_train_job green button"> | |||
{{.i18n.Tr "repo.cloudbrain.new"}} | |||
</button> | |||
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
</div> | |||
<!-- 模态框 --> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
const RepoLink = {{.RepoLink}} | |||
const url_href = window.location.pathname.split('create')[0] | |||
let nameMap,nameList | |||
$(".ui.button").attr('href',url_href) | |||
// 获取模型列表和模型名称对应的模型版本 | |||
$.get(`${RepoLink}/modelmanage/query_model_for_predict`, (data) => { | |||
nameMap = data.nameMap | |||
nameList = data.nameList | |||
let html = '' | |||
nameList.forEach(element => { | |||
html += `<div class="item" data-value=${element}>${element}</div>` | |||
}); | |||
if(nameList.length!==0){ | |||
const initModelVersion = nameMap[nameList[0]][0] | |||
const initTrainTaskInfo = JSON.parse(initModelVersion.TrainTaskInfo) | |||
$('#model_name').append(html) | |||
$("#select_model").dropdown('set text',nameList[0]) | |||
$("#select_model").dropdown('set value',nameList[0],nameList[0]) | |||
} | |||
$('#select_model').removeClass("loading") | |||
}) | |||
// 根据选中的模型名称获取相应的模型版本 | |||
$(function(){ | |||
$('#select_model').dropdown({ | |||
onChange: function(value, text, $selectedItem) { | |||
$("#select_model_version").addClass("loading") | |||
$('#model_name_version').empty() | |||
let html = '' | |||
nameMap[value].forEach(element => { | |||
let {TrainTaskInfo} = element | |||
TrainTaskInfo = JSON.parse(TrainTaskInfo) | |||
html += `<div class="item" data-label="${element.Label}" data-id="${element.ID}" data-value="${TrainTaskInfo.TrainUrl}">${element.Version}</div>` | |||
}); | |||
$('#model_name_version').append(html) | |||
$("#select_model_version").removeClass("loading") | |||
const initVersionText = $('#model_name_version div.item:first-child').text() | |||
const initVersionValue = $('#model_name_version div.item:first-child').data('value') | |||
$("#select_model_version").dropdown('set text',initVersionText) | |||
$("#select_model_version").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child')) | |||
} | |||
}) | |||
}) | |||
// 根据选中的模型版本获取相应的模型权重文件 | |||
$(function(){ | |||
$('#select_model_version').dropdown({ | |||
onChange: function(value, text, $selectedItem) { | |||
const dataID = $selectedItem[0].getAttribute("data-id") | |||
const label = $selectedItem[0].getAttribute("data-label") | |||
$("#select_model_checkpoint").addClass("loading") | |||
$("#model_checkpoint").empty() | |||
let html = '' | |||
loadCheckpointList(dataID).then((res)=>{ | |||
res.forEach(element => { | |||
const ckptSuffix = element.FileName.split(".") | |||
const loadCheckpointFile = ['ckpt','pb','h5','json','pkl','pth','t7'] | |||
if(!element.IsDir && loadCheckpointFile.includes(ckptSuffix[ckptSuffix.length-1])){ | |||
html += `<div class="item" data-value=${element.FileName}>${element.FileName}</div>` | |||
} | |||
}) | |||
$('#model_checkpoint').append(html) | |||
$("#select_model_checkpoint").removeClass("loading") | |||
const initVersionText = $('#model_checkpoint div.item:first-child').text() | |||
const initVersionValue = $('#model_checkpoint div.item:first-child').data('value') | |||
$("#select_model_checkpoint").dropdown('set text',initVersionText) | |||
$("#select_model_checkpoint").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child')) | |||
}) | |||
$("input#ai_model_version").val(text) | |||
$("input#ai_model_label").val(label) | |||
} | |||
}) | |||
}) | |||
function loadCheckpointList(value){ | |||
return new Promise((resolve,reject)=>{ | |||
$.get(`${RepoLink}/modelmanage/query_modelfile_for_predict`,{ID:value}, (data) => { | |||
resolve(data) | |||
}) | |||
}) | |||
} | |||
$('.question.circle.icon').hover(function(){ | |||
$(this).popup('show') | |||
}); | |||
// 参数增加、删除、修改、保存 | |||
function Add_parameter(i){ | |||
value = '<div class="two fields width84" id= "para'+ i +'">' + | |||
'<div class="field">' + | |||
'<input type="text" name="shipping_first-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_name"}}> ' + | |||
'</div> ' + | |||
'<div class="field"> ' + | |||
'<input type="text" name="shipping_last-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_value"}}>' + | |||
'</div>'+ | |||
'<span>' + | |||
'<i class="trash icon">' + | |||
'</i>' + | |||
'</span>' + | |||
'</div>' | |||
$(".dynamic.field").append(value) | |||
} | |||
$('#add_run_para').click(function(){ | |||
var len = $(".dynamic.field .two.fields").length | |||
Add_parameter(len) | |||
}); | |||
$(".dynamic.field").on("click",".trash.icon", function() { | |||
var index = $(this).parent().parent().index() | |||
$(this).parent().parent().remove() | |||
var len = $(".dynamic.field .two.fields").length | |||
$(".dynamic.field .two.fields").each(function(){ | |||
var cur_index = $(this).index() | |||
$(this).attr('id', 'para' + cur_index) | |||
}) | |||
}); | |||
function send_run_para(){ | |||
var run_parameters = [] | |||
var msg = {} | |||
$(".dynamic.field .two.fields").each(function(){ | |||
var para_name = $(this).find('input[name=shipping_first-name]').val() | |||
var para_value = $(this).find('input[name=shipping_last-name]').val() | |||
run_parameters.push({"label": para_name, "value": para_value}) | |||
}) | |||
msg["parameter"] = run_parameters | |||
msg = JSON.stringify(msg) | |||
$('#store_run_para').val(msg) | |||
} | |||
function get_name(){ | |||
let name1=$("#engine_name .text").text() | |||
let name2=$("#flaver_name .text").text() | |||
$("input#ai_engine_name").val(name1) | |||
$("input#ai_flaver_name").val(name2) | |||
} | |||
function validate(){ | |||
$('.ui.form') | |||
.form({ | |||
on: 'blur', | |||
fields: { | |||
boot_file: { | |||
identifier : 'boot_file', | |||
rules: [ | |||
{ | |||
type: 'regExp[/.+\.py$/g]', | |||
} | |||
] | |||
}, | |||
job_name:{ | |||
identifier : 'job_name', | |||
rules: [ | |||
{ | |||
type: 'regExp[/^[a-zA-Z0-9-_]{1,64}[^-]$/]', | |||
} | |||
] | |||
}, | |||
attachment:{ | |||
identifier : 'attachment', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
model_name:{ | |||
identifier : 'model_name', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
train_url:{ | |||
identifier : 'train_url', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
ckpt_name:{ | |||
identifier : 'ckpt_name', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
} | |||
}, | |||
onSuccess: function(){ | |||
document.getElementById("mask").style.display = "block" | |||
}, | |||
onFailure: function(e){ | |||
return false; | |||
} | |||
}) | |||
} | |||
document.onreadystatechange = function() { | |||
if (document.readyState === "complete") { | |||
document.getElementById("mask").style.display = "none" | |||
} | |||
} | |||
$('.ui.create_train_job.green.button').click(function(e) { | |||
send_run_para() | |||
get_name() | |||
validate() | |||
}) | |||
</script> |
@@ -0,0 +1,653 @@ | |||
{{template "base/head" .}} | |||
<style> | |||
.according-panel-heading{ | |||
box-sizing: border-box; | |||
padding: 8px 16px; | |||
color: #252b3a; | |||
background-color: #f2f5fc; | |||
line-height: 1.5; | |||
cursor: pointer; | |||
-moz-user-select: none; | |||
-webkit-user-select: none; | |||
-ms-user-select: none; | |||
-khtml-user-select: none; | |||
user-select: none; | |||
} | |||
.accordion-panel-title { | |||
margin-top: 0; | |||
margin-bottom: 0; | |||
color: #252b3a; | |||
} | |||
.accordion-panel-title-content{ | |||
vertical-align: middle; | |||
display: inline-block; | |||
width: calc(100% - 32px); | |||
cursor: default; | |||
} | |||
.acc-margin-bottom { | |||
margin-bottom: 5px; | |||
} | |||
.title_text { | |||
font-size: 12px; | |||
} | |||
.ac-display-inblock { | |||
display: inline-block; | |||
} | |||
.cti-mgRight-sm { | |||
margin-right: 8px; | |||
} | |||
.ac-text-normal { | |||
font-size: 14px; | |||
color: #575d6c; | |||
} | |||
.uc-accordionTitle-black { | |||
color: #333; | |||
} | |||
.accordion-border{ | |||
border:1px solid #cce2ff; | |||
} | |||
.padding0{ | |||
padding: 0 !important; | |||
} | |||
.content-pad{ | |||
padding: 15px 35px; | |||
} | |||
.content-margin{ | |||
margin:10px 5px ; | |||
} | |||
.tab_2_content { | |||
min-height: 360px; | |||
margin-left: 10px; | |||
} | |||
.ac-grid { | |||
display: block; | |||
*zoom: 1; | |||
} | |||
.ac-grid-col { | |||
float: left; | |||
width: 100%; | |||
} | |||
.ac-grid-col2 .ac-grid-col { | |||
width: 50%; | |||
} | |||
.ti-form { | |||
text-align: left; | |||
max-width: 100%; | |||
vertical-align: middle; | |||
} | |||
.ti-form>tbody { | |||
font-size: 12px; | |||
} | |||
.ti-form>tbody, .ti-form>tbody>tr { | |||
vertical-align: inherit; | |||
} | |||
.ti-text-form-label { | |||
padding-bottom: 20px; | |||
padding-right: 20px; | |||
color: #8a8e99; | |||
font-size: 12px; | |||
white-space: nowrap !important; | |||
width: 80px; | |||
line-height: 30px; | |||
} | |||
.ti-text-form-content{ | |||
line-height: 30px; | |||
padding-bottom: 20px; | |||
} | |||
.ti-form>tbody>tr>td { | |||
vertical-align: top; | |||
white-space: normal; | |||
} | |||
td, th { | |||
padding: 0; | |||
} | |||
.ac-grid-col .text-span { | |||
width: 450px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.redo-color{ | |||
color: #3291F8; | |||
} | |||
.ti-action-menu-item:not(:last-child){ | |||
margin-right: 10px; | |||
padding-right: 11px; | |||
text-decoration: none!important; | |||
color: #526ecc; | |||
cursor: pointer; | |||
display: inline-block; | |||
-moz-user-select: none; | |||
-webkit-user-select: none; | |||
-ms-user-select: none; | |||
-khtml-user-select: none; | |||
user-select: none; | |||
position: relative; | |||
} | |||
.ti-action-menu-item:not(:last-child):after { | |||
content: ""; | |||
display: inline-block; | |||
position: absolute; | |||
height: 12px; | |||
right: 0; | |||
top: 50%; | |||
-webkit-transform: translateY(-6px); | |||
-ms-transform: translateY(-6px); | |||
-o-transform: translateY(-6px); | |||
transform: translateY(-6px); | |||
border-right: 1px solid #dfe1e6; | |||
} | |||
.text-width80{ | |||
width: 100px; | |||
line-height: 30px; | |||
} | |||
.border-according{ | |||
border: 1px solid #dfe1e6; | |||
} | |||
.disabled { | |||
cursor: default; | |||
pointer-events: none; | |||
color: rgba(0,0,0,.6) !important; | |||
opacity: .45 !important; | |||
} | |||
.pad20{ | |||
border:0px !important; | |||
} | |||
.model_file_bread{ | |||
margin-bottom: -0.5rem !important; | |||
padding-left: 1rem; | |||
padding-top: 0.5rem ; | |||
} | |||
</style> | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="ui container"> | |||
<h4 class="ui header" id="vertical-segment"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all"> | |||
{{.i18n.Tr "repo.cloudbrain"}} | |||
</a> | |||
<div class="divider"> / </div> | |||
<a class="section" href="{{$.RepoLink}}/modelarts/inference-job"> | |||
{{$.i18n.Tr "repo.modelarts.infer_job"}} | |||
</a> | |||
<div class="divider"> / </div> | |||
<div class="active section">{{.jobName}}</div> | |||
</div> | |||
</h4> | |||
{{with .task}} | |||
<div class="content-pad" style="border: 1px solid #e2e2e2;margin-top: 24px;padding: 20px 60px 40px 60px;"> | |||
<div class="ui pointing secondary menu" style="border-bottom: 1px solid rgba(34,36,38,.15);"> | |||
<a class="active item" data-tab="first">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a> | |||
<a class="item" data-tab="second" onclick="loadLog({{.VersionName}})">{{$.i18n.Tr "repo.modelarts.log"}}</a> | |||
<a class="item" data-tab="third" onclick="loadModelFile({{.VersionName}},'','','init')">{{$.i18n.Tr "repo.model_download"}}</a> | |||
</div> | |||
<div class="ui tab active" data-tab="first"> | |||
<div style="padding-top: 10px;"> | |||
<div class="tab_2_content"> | |||
<div class="ac-grid ac-grid-col2"> | |||
<div class="ac-grid-col"> | |||
<table class="ti-form"> | |||
<tbody class="ti-text-form"> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.cloudbrain_task"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.JobName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.status"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-status"> | |||
{{.Status}} | |||
</div> | |||
</td> | |||
</tr> | |||
<!-- <tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.run_version"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.VersionName}} | |||
</div> | |||
</td> | |||
</tr> --> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.start_time"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .CreatedUnix}}</span> | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-duration"> | |||
{{.TrainJobDuration}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.EngineName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.model.manage.description"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-desc" style="width: 380px;"> | |||
{{if .Description}} | |||
<span title="{{.Description}}">{{.Description}}</span> | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
创建人 | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-creator"> | |||
{{$.userName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.WorkServerNumber}} | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
<div class="ac-grid-col"> | |||
<table class="ti-form"> | |||
<tbody class="ti-text-form"> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.infer_job_model"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
<span>{{.ModelName}}</span> | |||
<span style="color: #8a8e99">{{$.i18n.Tr "repo.modelarts.version"}}:</span><span>{{.ModelVersion}}</span> | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.infer_job_model_file"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.CkptName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.model_label"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-labels"> | |||
{{if .LabelName}} | |||
{{range $.labelName}} | |||
<a class="ui label" title="{{.}}">{{.}}</a> | |||
{{end}} | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.code_version"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.BranchName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.BootFile}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.infer_dataset"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.DatasetName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80" > | |||
{{$.i18n.Tr "repo.modelarts.train_job.run_parameter"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" title="{{.Parameters}}"> | |||
{{if .Parameters}} | |||
<span>{{.Parameters}}</span> | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.standard"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.FlavorName}} | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui tab" data-tab="second"> | |||
<div> | |||
<div class="ui message message{{.VersionName}}" style="display: none;"> | |||
<div id="header"></div> | |||
</div> | |||
<div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;"> | |||
<input type="hidden" name="end_line" value> | |||
<input type="hidden" name="start_line" value> | |||
<pre id="log_file{{.VersionName}}"></pre> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui tab" data-tab="third"> | |||
<input type="hidden" name="model{{.VersionName}}" value="-1"> | |||
<input type="hidden" name="modelback{{.VersionName}}" value="-1"> | |||
<div class='ui breadcrumb model_file_bread' id='file_breadcrumb{{.VersionName}}'> | |||
<div class="active section">result</div> | |||
<div class="divider"> / </div> | |||
</div> | |||
<div id="dir_list{{.VersionName}}"> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div id="deletemodel"> | |||
<div class="ui basic modal"> | |||
<div class="ui icon header"> | |||
<i class="trash icon"></i> 删除任务 | |||
</div> | |||
<div class="content"> | |||
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p> | |||
</div> | |||
<div class="actions"> | |||
<div class="ui red basic inverted cancel button"> | |||
<i class="remove icon"></i> 取消操作 | |||
</div> | |||
<div class="ui green basic inverted ok button"> | |||
<i class="checkmark icon"></i> 确定操作 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
console.log({{.task}}) | |||
$(document).ready(function(){ | |||
$('.secondary.menu .item').tab(); | |||
}); | |||
let userName | |||
let repoPath | |||
let jobID | |||
$(document).ready(function(){ | |||
let url = window.location.href; | |||
let urlArr = url.split('/') | |||
userName = urlArr.slice(-5)[0] | |||
repoPath = urlArr.slice(-4)[0] | |||
jobID = urlArr.slice(-1)[0] | |||
}) | |||
function loadLog(version_name){ | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => { | |||
$('input[name=end_line]').val(data.EndLine) | |||
$('input[name=start_line]').val(data.StartLine) | |||
$(`#log_file${version_name}`).text(data.Content) | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function logScroll(version_name) { | |||
let container = document.querySelector(`#log${version_name}`) | |||
let scrollTop = container.scrollTop | |||
let scrollHeight = container.scrollHeight | |||
let clientHeight = container.clientHeight | |||
let scrollLeft = container.scrollLeft | |||
if((parseInt(scrollTop) + clientHeight == scrollHeight || parseInt(scrollTop) + clientHeight +1 == scrollHeight || parseInt(scrollTop) + clientHeight - 1 == scrollHeight) && (scrollLeft===0)){ | |||
let end_line = $(`#log${version_name} input[name=end_line]`).val() | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${end_line}&lines=50&order=desc`, (data) => { | |||
if (data.Lines == 0){ | |||
$(`.message${version_name} #header`).text('您已翻阅至日志底部') | |||
$(`.message${version_name}`).css('display', 'block') | |||
setTimeout(function(){ | |||
$(`.message${version_name}`).css('display', 'none') | |||
}, 1000) | |||
}else{ | |||
if(end_line===data.EndLine){ | |||
return | |||
} | |||
else{ | |||
$(`#log${version_name} input[name=end_line]`).val(data.EndLine) | |||
$(`#log${version_name}`).append('<pre>' + data.Content) | |||
} | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
if(scrollTop == 0 && scrollLeft==0){ | |||
let start_line = $(`#log${version_name} input[name=start_line]`).val() | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${start_line}&lines=50&order=asc`, (data) => { | |||
if (data.Lines == 0){ | |||
$(`.message${version_name} #header`).text('您已翻阅至日志顶部') | |||
$(`.message${version_name}`).css('display', 'block') | |||
setTimeout(function(){ | |||
$(`.message${version_name}`).css('display', 'none') | |||
}, 1000) | |||
}else{ | |||
$(`#log${version_name} input[name=start_line]`).val(data.StartLine) //如果变动就改变所对应的值 | |||
$(`#log${version_name}`).prepend('<pre>' + data.Content) | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
} | |||
function renderSize(value){ | |||
if(null==value||value==''){ | |||
return "0 Bytes"; | |||
} | |||
var unitArr = new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"); | |||
var index=0; | |||
var srcsize = parseFloat(value); | |||
index=Math.floor(Math.log(srcsize)/Math.log(1024)); | |||
var size =srcsize/Math.pow(1024,index); | |||
size=size.toFixed(0);//保留的小数位数 | |||
return size+unitArr[index]; | |||
} | |||
function loadModelFile(version_name,parents,filename,init){ | |||
parents = parents || '' | |||
filename = filename || '' | |||
init = init || '' | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/result_list?version_name=${version_name}&parentDir=${parents}`, (data) => { | |||
$(`#dir_list${version_name}`).empty() | |||
renderDir(data,version_name) | |||
if(init==="init"){ | |||
$(`input[name=model${version_name}]`).val("") | |||
$(`input[name=modelback${version_name}]`).val(version_name) | |||
$(`#file_breadcrumb${version_name}`).empty() | |||
let htmlBread = "" | |||
htmlBread += `<div class='active section'>result</div>` | |||
htmlBread += "<div class='divider'> / </div>" | |||
$(`#file_breadcrumb${version_name}`).append(htmlBread) | |||
}else{ | |||
renderBrend(version_name,parents,filename,init) | |||
} | |||
}).fail(function(err) { | |||
console.log(err,version_name); | |||
}); | |||
} | |||
function renderBrend(version_name,parents,filename,init){ | |||
if(init=="folder"){ | |||
let htmlBrend = "" | |||
let sectionName=$(`#file_breadcrumb${version_name} .active.section`).text() | |||
let parents1 = $(`input[name=model${version_name}]`).val() | |||
let filename1 = $(`input[name=modelback${version_name}]`).val() | |||
if(parents1===""){ | |||
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','','init')">${sectionName}</a>`) | |||
}else{ | |||
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','${filename1}')">${sectionName}</a>`) | |||
} | |||
htmlBrend += `<div class='active section'>${filename}</div>` | |||
htmlBrend += "<div class='divider'> / </div>" | |||
$(`#file_breadcrumb${version_name}`).append(htmlBrend) | |||
$(`input[name=model${version_name}]`).val(parents) | |||
$(`input[name=modelback${version_name}]`).val(filename) | |||
}else{ | |||
$(`input[name=model${version_name}]`).val(parents) | |||
$(`input[name=modelback${version_name}]`).val(filename) | |||
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).nextAll().remove() | |||
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).replaceWith(`<div class='active section'>${filename}</div>`) | |||
$(`#file_breadcrumb${version_name} div.section:contains(${filename})`).append("<div class='divider'> / </div>") | |||
} | |||
} | |||
function renderDir(data,version_name){ | |||
let html="" | |||
html += "<div class='ui grid' style='margin:0;'>" | |||
html += "<div class='row' style='padding: 0;'>" | |||
html += "<div class='ui sixteen wide column' style='padding:1rem;'>" | |||
html += "<div class='dir list'>" | |||
html += "<table id='repo-files-table' class='ui single line table pad20'>" | |||
html += '<tbody>' | |||
// html += "</tbody>" | |||
for(let i=0;i<data.Dirs.length;i++){ | |||
let dirs_size = renderSize(data.Dirs[i].Size) | |||
html += "<tr>" | |||
html += "<td class='name six wid'>" | |||
html += "<span class='truncate'>" | |||
html += "<span class='octicon octicon-file-directory'>" | |||
html += "</span>" | |||
if(data.Dirs[i].IsDir){ | |||
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">` | |||
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>" | |||
}else{ | |||
html += `<a href="${location.href}/result_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">` | |||
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>" | |||
} | |||
html += '</a>' | |||
html += "</span>" | |||
html += "</td>" | |||
html += "<td class='message seven wide'>" | |||
if(data.Dirs[i].IsDir){ | |||
html += "<span class='truncate has-emoji'></span>" | |||
}else{ | |||
html += "<span class='truncate has-emoji'>"+ `${dirs_size}` + "</span>" | |||
} | |||
html += "</td>" | |||
html += "<td class='text right age three wide'>" | |||
html += "<span class='truncate has-emoji'>" + data.Dirs[i].ModTime + "</span>" | |||
html += "</td>" | |||
html += "</tr>" | |||
} | |||
html += "</tbody>" | |||
html += "</table>" | |||
html += "</div>" | |||
html += "</div>" | |||
html += "</div>" | |||
html += "</div>" | |||
$(`#dir_list${version_name}`).append(html) | |||
} | |||
</script> |
@@ -48,7 +48,7 @@ | |||
</div> | |||
<div class="inline required field"> | |||
<label>任务名称</label> | |||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254"> | |||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" onkeyup="this.value=this.value.replace(/[, ]/g,'')"> | |||
</div> | |||
<div class="inline field"> | |||
@@ -64,11 +64,11 @@ | |||
<div class="inline required field"> | |||
<label>工作环境</label> | |||
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>类型</label> | |||
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>规格</label> | |||
@@ -81,11 +81,11 @@ | |||
</div> | |||
<div class="inline required field"> | |||
<label>数据集存放路径</label> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly"> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline field"> | |||
<label>描述</label> | |||
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="254"> | |||
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="255"> | |||
</div> | |||
<div class="inline field"> | |||
<label></label> | |||
@@ -18,11 +18,11 @@ | |||
<h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}</h4> | |||
<div class="required field"> | |||
<label>{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> | |||
<input name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="254" readonly=""> | |||
<input name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" readonly=""> | |||
</div> | |||
<div class="field"> | |||
<label for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label> | |||
<textarea id="description" maxlength="254" name="description" rows="2"></textarea> | |||
<textarea id="description" maxlength="255" name="description" rows="2"></textarea> | |||
</div> | |||
<h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}</h4> | |||
<div class="required field"> | |||
@@ -52,7 +52,7 @@ | |||
</div> | |||
<div class="inline required field"> | |||
<label>{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> | |||
<input name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254"> | |||
<input name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255"> | |||
<span> | |||
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> | |||
</span> | |||
@@ -128,7 +128,7 @@ | |||
</div> | |||
<div class="inline required field"> | |||
<label>{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> | |||
<input name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254"> | |||
<input name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255"> | |||
</div> | |||
<div class="inline field"> | |||
<button class="ui green button"> | |||
@@ -34,6 +34,7 @@ | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
@@ -141,11 +142,11 @@ | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | |||
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic disabled button"> | |||
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{end}} | |||
@@ -262,7 +263,8 @@ | |||
const repoPath = job.dataset.repopath | |||
const versionname = job.dataset.version | |||
const status_text = $(`#${jobID}-text`).text() | |||
if(['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED'].includes(status_text)){ | |||
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED'] | |||
if(finalState.includes(status_text)){ | |||
return | |||
} | |||
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => { | |||
@@ -273,6 +275,7 @@ | |||
if (status != job.textContent.trim()) { | |||
$('#' + jobID+'-icon').removeClass().addClass(status) | |||
$('#' + jobID+ '-text').text(status) | |||
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled') | |||
} | |||
@@ -80,12 +80,13 @@ | |||
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> | |||
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="254"> | |||
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" onkeyup="this.value=this.value.replace(/[, ]/g,'')" autofocus required maxlength="64"> | |||
<span class="tooltips" style="display: block;">请输入字母、数字、_和-,最长64个字符,且不能以中划线(-)结尾。</span> | |||
</div> | |||
<div class="unite min_title inline field"> | |||
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}} </label> | |||
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea> | |||
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea> | |||
</div> | |||
<div class="ui divider"></div> | |||
@@ -110,7 +111,6 @@ | |||
{{end}} | |||
{{end}} | |||
{{end}} | |||
</select> | |||
</div> | |||
@@ -140,9 +140,9 @@ | |||
<div class="inline unite min_title field required"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> | |||
{{if .bootFile}} | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="254" > | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="255" > | |||
{{else}} | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" > | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" > | |||
{{end}} | |||
<span> | |||
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> | |||
@@ -226,7 +226,7 @@ | |||
<div class="ui labeled input" style="width: 5%;"> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="1" readonly> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="1" readonly> | |||
</div> | |||
@@ -312,7 +312,6 @@ | |||
}) | |||
}); | |||
console.log(parameters) | |||
$('.ui.parameter.modal') | |||
.modal('hide'); | |||
for(var i = 2; i < parameters.length; i++){ | |||
@@ -379,65 +378,16 @@ | |||
$('select.dropdown') | |||
.dropdown(); | |||
$('.ui.form') | |||
.form({ | |||
on: 'blur', | |||
inline:true, | |||
fields: { | |||
boot_file: { | |||
identifier : 'boot_file', | |||
rules: [ | |||
{ | |||
type: 'regExp[/.+\.py$/g]', | |||
prompt : '启动文件必须为.py结尾' | |||
} | |||
] | |||
}, | |||
job_name:{ | |||
identifier : 'job_name', | |||
rules: [ | |||
{ | |||
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]', | |||
prompt : '只包含大小写字母、数字、_和-,最长36个字符。' | |||
} | |||
] | |||
}, | |||
attachment:{ | |||
identifier : 'attachment', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
prompt : '选择一个数据集' | |||
} | |||
] | |||
}, | |||
work_server_number: { | |||
identifier : 'work_server_number', | |||
rules: [ | |||
{ | |||
type : 'integer[1..25]', | |||
prompt : '计算节点需要在1-25之间,请您键入正确的值' | |||
} | |||
] | |||
} | |||
}, | |||
}) | |||
function validate(){ | |||
$('.ui.form') | |||
.form({ | |||
on: 'blur', | |||
inline:true, | |||
fields: { | |||
boot_file: { | |||
identifier : 'boot_file', | |||
rules: [ | |||
{ | |||
type: 'regExp[/.+\.py$/g]', | |||
prompt : '启动文件必须为.py结尾' | |||
} | |||
] | |||
}, | |||
@@ -445,8 +395,7 @@ | |||
identifier : 'job_name', | |||
rules: [ | |||
{ | |||
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]', | |||
prompt : '只包含大小写字母、数字、_和-,最长36个字符。' | |||
type: 'regExp[/^[a-zA-Z0-9-_]{1,64}[^-]$/]', | |||
} | |||
] | |||
}, | |||
@@ -455,7 +404,6 @@ | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
prompt : '选择一个数据集' | |||
} | |||
] | |||
@@ -465,7 +413,6 @@ | |||
rules: [ | |||
{ | |||
type : 'integer[1..25]', | |||
prompt : '计算节点需要在1-25之间,请您键入正确的值' | |||
} | |||
] | |||
} | |||
@@ -175,7 +175,7 @@ td, th { | |||
<div class="ui container"> | |||
<h4 class="ui header" id="vertical-segment"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/cloudbrain"> | |||
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all"> | |||
{{.i18n.Tr "repo.cloudbrain"}} | |||
</a> | |||
<div class="divider"> / </div> | |||
@@ -196,25 +196,25 @@ td, th { | |||
<span> | |||
<div style="float: right;"> | |||
{{$.CsrfTokenHtml}} | |||
{{if and (.CanModify) (eq .Status "COMPLETED")}} | |||
<a class="ti-action-menu-item" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a> | |||
{{if and (.CanModify) (eq .Status "COMPLETED") ($.Permission.CanWrite $.UnitTypeModelManage) }} | |||
<a class="ti-action-menu-item" id="{{.VersionName}}-create-model" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a> | |||
{{else}} | |||
<a class="ti-action-menu-item disabled">{{$.i18n.Tr "repo.modelarts.create_model"}}</a> | |||
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-create-model" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a> | |||
{{end}} | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanModify}} | |||
<a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | |||
{{else}} | |||
<a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | |||
{{end}} | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | |||
{{else}} | |||
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | |||
{{end}} | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | |||
{{else}} | |||
@@ -505,7 +505,7 @@ td, th { | |||
</div> | |||
<div class="inline field" style="margin-left: 75px;"> | |||
<button onclick="createModel()" id="submitId" type="button" class="ui create_train_job green button" style="position: absolute;"> | |||
<button onclick="createModel()" type="button" class="ui create_train_job green button" style="position: absolute;"> | |||
{{.i18n.Tr "repo.model.manage.sava_model"}} | |||
</button> | |||
</div> | |||
@@ -522,7 +522,6 @@ td, th { | |||
<script> | |||
$('.menu .item').tab() | |||
$(document).ready(function(){ | |||
$('.ui.accordion').accordion({selector:{trigger:'.icon'}}); | |||
}); | |||
@@ -614,7 +613,7 @@ td, th { | |||
var srcsize = parseFloat(value); | |||
index=Math.floor(Math.log(srcsize)/Math.log(1024)); | |||
var size =srcsize/Math.pow(1024,index); | |||
size=size.toFixed(2);//保留的小数位数 | |||
size=size.toFixed(0);//保留的小数位数 | |||
return size+unitArr[index]; | |||
} | |||
function loadJobStatus() { | |||
@@ -644,6 +643,9 @@ td, th { | |||
if(stopArray.includes(data.JobStatus)){ | |||
$('#'+versionname+'-stop').addClass('disabled') | |||
} | |||
if(data.JobStatus==="COMPLETED"){ | |||
$('#'+versionname+'-create-model').removeClass('disabled').addClass('blue') | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
@@ -99,7 +99,7 @@ | |||
<div class="unite min_title inline field"> | |||
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}} </label> | |||
<textarea style="width: 80%;" id="description" value="{{.description}}" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)">{{.description}}</textarea> | |||
<textarea style="width: 80%;" id="description" value="{{.description}}" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)">{{.description}}</textarea> | |||
</div> | |||
<div class="ui divider"></div> | |||
@@ -152,9 +152,9 @@ | |||
<div class="inline unite min_title field required"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> | |||
{{if .boot_file}} | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.boot_file}}" tabindex="3" autofocus required maxlength="254" > | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.boot_file}}" tabindex="3" autofocus required maxlength="255" > | |||
{{else}} | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" > | |||
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" > | |||
{{end}} | |||
<span> | |||
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> | |||
@@ -245,7 +245,7 @@ | |||
<div class="ui labeled input" style="width: 5%;"> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="{{.work_server_number}}" readonly> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="{{.work_server_number}}" readonly> | |||
</div> | |||
@@ -23,7 +23,7 @@ | |||
<div class="column"></div> | |||
<div class="column right aligned"> | |||
<!-- --> | |||
<a class="ui button {{if .Permission.CanWrite $.UnitTypeCloudBrain}} green {{else}} disabled {{end}}" onclick="showcreate(this)">{{$.i18n.Tr "repo.model.manage.import_new_model"}}</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> | |||
</div> | |||
</div> | |||
{{if eq $.MODEL_COUNT 0}} | |||
@@ -38,6 +38,10 @@ | |||
<div class="bgtask-content-txt">训练任务:您还没创建过训练任务,请先创建<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>。</div> | |||
{{end}} | |||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
</div> | |||
<div style="display: none;"> | |||
<div id="model_list"></div> | |||
</div> | |||
</div> | |||
{{else}} | |||
@@ -90,6 +94,7 @@ | |||
</div> | |||
<div class="content content-padding"> | |||
<form id="formId" method="POST" class="ui form"> | |||
<input type="hidden" name="initModel" value="{{$.MODEL_COUNT}}"> | |||
<div class="ui error message"> | |||
<!-- <p>asdasdasd</p> --> | |||
</div> | |||
@@ -53,7 +53,7 @@ | |||
</div> | |||
<div class="inline field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> | |||
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea> | |||
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea> | |||
</div> | |||
<div class="inline field"> | |||
@@ -19,7 +19,7 @@ | |||
{{if .PageIsEditRelease}} | |||
<b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong> | |||
{{else}} | |||
<input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.i18n.Tr "repo.release.tag_name"}}" autofocus required maxlength="254"> | |||
<input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.i18n.Tr "repo.release.tag_name"}}" autofocus required maxlength="255"> | |||
<span class="at">@</span> | |||
<div class="ui selection dropdown"> | |||
<input type="hidden" name="tag_target" value="{{.tag_target}}"/> | |||
@@ -42,7 +42,7 @@ | |||
<div class="eleven wide column"> | |||
<div class="field {{if .Err_Title}}error{{end}}"> | |||
<label>{{.i18n.Tr "repo.release.title"}}</label> | |||
<input name="title" placeholder="{{.i18n.Tr "repo.release.title"}}" value="{{.title}}" autofocus required maxlength="254"> | |||
<input name="title" placeholder="{{.i18n.Tr "repo.release.title"}}" value="{{.title}}" autofocus required maxlength="255"> | |||
</div> | |||
<div class="field"> | |||
<label>{{.i18n.Tr "repo.release.content"}}</label> | |||
@@ -41,7 +41,7 @@ | |||
{{end}} | |||
<div class="field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label> | |||
<textarea id="description" name="description" rows="2" maxlength="254">{{.Repository.Description}}</textarea> | |||
<textarea id="description" name="description" rows="2" maxlength="255">{{.Repository.Description}}</textarea> | |||
</div> | |||
<div class="field {{if .Err_Website}}error{{end}}"> | |||
<label for="website">{{.i18n.Tr "repo.settings.site"}}</label> | |||
@@ -152,7 +152,7 @@ | |||
{{$isModelMangeEnabled := .Repository.UnitEnabled $.UnitTypeModelManage }} | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.model_manager"}}</label> | |||
<div class="ui checkbox"> | |||
<div class="ui checkbox {{if ne $.MODEL_COUNT 0}}disabled{{end}}"> | |||
<input class="enable-system" name="enable_model_manager" type="checkbox" {{if $isModelMangeEnabled}}checked{{end}}> | |||
<label>{{.i18n.Tr "repo.settings.model_desc"}}</label> | |||
</div> | |||
@@ -160,7 +160,7 @@ | |||
{{$isCloudBrainEnabled := .Repository.UnitEnabled $.UnitTypeCloudBrain }} | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.cloudbrain"}}</label> | |||
<div class="ui checkbox"> | |||
<div class="ui checkbox {{if ne $.jobCount 0}}disabled{{end}}"> | |||
<input class="enable-system" name="enable_cloud_brain" type="checkbox" {{if $isCloudBrainEnabled}}checked{{end}}> | |||
<label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label> | |||
</div> | |||
@@ -625,4 +625,4 @@ | |||
{{end}} | |||
{{end}} | |||
{{template "base/footer" .}} | |||
{{template "base/footer" .}} |
@@ -34,7 +34,7 @@ | |||
</div> | |||
<div class="field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{$.i18n.Tr "user.user_bio"}}</label> | |||
<textarea id="description" name="description" rows="2" maxlength="254">{{.SignedUser.Description}}</textarea> | |||
<textarea id="description" name="description" rows="2" maxlength="255">{{.SignedUser.Description}}</textarea> | |||
</div> | |||
<div class="field {{if .Err_Website}}error{{end}}"> | |||
<label for="website">{{.i18n.Tr "settings.website"}}</label> | |||
@@ -106,7 +106,7 @@ | |||
<div class="space-around"> | |||
<a :style="{visibility:!scope.row.Children ? 'visible':'hidden'}" :class="{'disabled':!scope.row.IsCanOper}" @click="showcreateVue(scope.row.Name,scope.row.Version,scope.row.Label)">创建新版本</a> | |||
<a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a> | |||
<a :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a> | |||
<a :class="{'disabled':!scope.row.IsCanDelete}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a> | |||
</div> | |||
</template> | |||
@@ -263,8 +263,10 @@ export default { | |||
let cName = $("input[name='Name']").val() | |||
let version = $("input[name='Version']").val() | |||
let data = $("#formId").serialize() | |||
const initModel = $("input[name='initModel']").val() | |||
let url_href = version === '0.0.1' ? context.url_create_newModel : context.url_create_newVersion | |||
$("#mask").css({"display":"block","z-index":"9999"}) | |||
$.ajax({ | |||
url:url_href, | |||
type:'POST', | |||
@@ -273,6 +275,9 @@ export default { | |||
// context.loadrefresh1(row) | |||
context.getModelList() | |||
$('.ui.modal.second').modal('hide') | |||
if(initModel==='0'){ | |||
location.reload() | |||
} | |||
}, | |||
error: function(xhr){ | |||
// 隐藏 loading | |||
@@ -360,6 +365,9 @@ export default { | |||
this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true | |||
} | |||
this.totalNum = res.data.count | |||
// if(res.data.count===1 && res.data.data[0].VersionCount===1){ | |||
// location.reload() | |||
// } | |||
}) | |||
}catch (e) { | |||
console.log(e) | |||
@@ -4128,7 +4128,7 @@ function initDropDown() { | |||
} | |||
//云脑提示 | |||
$('.question.circle.icon').hover(function(){ | |||
$('.question.circle.icon.cloudbrain-question').hover(function(){ | |||
$(this).popup('show') | |||
$('.ui.popup.mini.top.center').css({"border-color":'rgba(50, 145, 248, 100)',"color":"rgba(3, 102, 214, 100)","border-radius":"5px","border-shadow":"none"}) | |||
}); | |||