diff --git a/models/cloudbrain.go b/models/cloudbrain.go index f40403fdd..af53bad32 100755 --- a/models/cloudbrain.go +++ b/models/cloudbrain.go @@ -1803,7 +1803,7 @@ func GetBenchmarkCountByUserID(userID int64) (int, error) { func GetWaitingCloudbrainCount(cloudbrainType int, computeResource string, jobTypes ...JobType) (int64, error) { sess := x.Where("status=? and type=?", JobWaiting, cloudbrainType) if len(jobTypes) > 0 { - sess.In("JobType", jobTypes) + sess.In("job_type", jobTypes) } if computeResource != "" { sess.And("compute_resource=?", computeResource) diff --git a/modules/auth/cloudbrain.go b/modules/auth/cloudbrain.go index 91fa8ad64..160328b5b 100755 --- a/modules/auth/cloudbrain.go +++ b/modules/auth/cloudbrain.go @@ -69,6 +69,7 @@ type CreateCloudBrainInferencForm struct { ModelName string `form:"model_name" binding:"Required"` ModelVersion string `form:"model_version" binding:"Required"` CkptName string `form:"ckpt_name" binding:"Required"` + LabelName string `form:"label_names" binding:"Required"` } func (f *CreateCloudBrainForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { diff --git a/modules/cloudbrain/cloudbrain.go b/modules/cloudbrain/cloudbrain.go index 02c4dc042..e09937df3 100755 --- a/modules/cloudbrain/cloudbrain.go +++ b/modules/cloudbrain/cloudbrain.go @@ -78,6 +78,7 @@ type GenerateCloudBrainTaskReq struct { ModelName string ModelVersion string CkptName string + LabelName string } func GetCloudbrainDebugCommand() string { @@ -400,6 +401,7 @@ func GenerateTask(req GenerateCloudBrainTaskReq) error { ModelVersion: req.ModelVersion, CkptName: req.CkptName, ResultUrl: req.ResultPath, + LabelName: req.LabelName, CreatedUnix: createTime, UpdatedUnix: createTime, CommitID: req.CommitID, diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 9a4774fa3..3d9452f93 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -925,7 +925,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/inference-job", func() { m.Group("/:jobid", func() { m.Get("", repo.GetCloudBrainInferenceJob) - m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.DelCloudBrainJob) + m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo.DelCloudBrainJob) m.Get("/result_list", repo.InferencJobResultList) }) }) diff --git a/routers/api/v1/repo/cloudbrain.go b/routers/api/v1/repo/cloudbrain.go index 5ebbc9b46..d291024f9 100755 --- a/routers/api/v1/repo/cloudbrain.go +++ b/routers/api/v1/repo/cloudbrain.go @@ -157,15 +157,15 @@ func DelCloudBrainJob(ctx *context.APIContext) { if errStr != "" { ctx.JSON(http.StatusOK, map[string]interface{}{ - "message": ctx.Tr(errStr), + "Message": ctx.Tr(errStr), "VersionName": "1", - "code": 1, + "Code": 1, }) } else { ctx.JSON(http.StatusOK, map[string]interface{}{ - "message": "", + "Message": "", "VersionName": "1", - "code": 0, + "Code": 0, }) } diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index 7b7bae62d..41d002962 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -131,10 +131,12 @@ func cloudBrainNewDataPrepare(ctx *context.Context) error { } ctx.Data["train_gpu_types"] = trainGpuInfos.GpuInfo - if inferenceGpuInfos == nil { + if inferenceGpuInfos == nil && setting.InferenceGpuTypes != "" { json.Unmarshal([]byte(setting.InferenceGpuTypes), &inferenceGpuInfos) } - ctx.Data["inference_gpu_types"] = inferenceGpuInfos.GpuInfo + if inferenceGpuInfos != nil { + ctx.Data["inference_gpu_types"] = inferenceGpuInfos.GpuInfo + } if benchmarkGpuInfos == nil { json.Unmarshal([]byte(setting.BenchmarkGpuTypes), &benchmarkGpuInfos) @@ -156,10 +158,12 @@ func cloudBrainNewDataPrepare(ctx *context.Context) error { } ctx.Data["train_resource_specs"] = cloudbrain.TrainResourceSpecs.ResourceSpec - if cloudbrain.InferenceResourceSpecs == nil { + if cloudbrain.InferenceResourceSpecs == nil && setting.InferenceResourceSpecs != "" { json.Unmarshal([]byte(setting.InferenceResourceSpecs), &cloudbrain.InferenceResourceSpecs) } - ctx.Data["inference_resource_specs"] = cloudbrain.InferenceResourceSpecs.ResourceSpec + if cloudbrain.InferenceResourceSpecs != nil { + ctx.Data["inference_resource_specs"] = cloudbrain.InferenceResourceSpecs.ResourceSpec + } if cloudbrain.SpecialPools != nil { var debugGpuTypes []*models.GpuInfo @@ -261,6 +265,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { } if !jobNamePattern.MatchString(displayJobName) { + cloudBrainNewDataPrepare(ctx) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tpl, &form) return } @@ -384,6 +389,7 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath resourceSpecId := form.ResourceSpecId branchName := form.BranchName + labelName := form.LabelName repo := ctx.Repo.Repository ckptUrl := setting.Attachment.Minio.RealPath + form.TrainUrl + form.CkptName @@ -479,6 +485,7 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra ModelVersion: form.ModelVersion, CkptName: form.CkptName, TrainUrl: form.TrainUrl, + LabelName: labelName, } err = cloudbrain.GenerateTask(req) @@ -660,14 +667,35 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName, jobType models.Jo if cloudbrain.TrainResourceSpecs == nil { json.Unmarshal([]byte(setting.TrainResourceSpecs), &cloudbrain.TrainResourceSpecs) } + hasSpec := false for _, tmp := range cloudbrain.TrainResourceSpecs.ResourceSpec { if tmp.Id == task.ResourceSpecId { + hasSpec = true ctx.Data["GpuNum"] = tmp.GpuNum ctx.Data["CpuNum"] = tmp.CpuNum ctx.Data["MemMiB"] = tmp.MemMiB ctx.Data["ShareMemMiB"] = tmp.ShareMemMiB } } + + if !hasSpec && cloudbrain.SpecialPools != nil { + for _, specialPool := range cloudbrain.SpecialPools.Pools { + + if specialPool.ResourceSpec != nil { + + for _, spec := range specialPool.ResourceSpec { + if task.ResourceSpecId == spec.Id { + ctx.Data["GpuNum"] = spec.GpuNum + ctx.Data["CpuNum"] = spec.CpuNum + ctx.Data["MemMiB"] = spec.MemMiB + ctx.Data["ShareMemMiB"] = spec.ShareMemMiB + break + } + } + } + } + } + } else if task.JobType == string(models.JobTypeInference) { if cloudbrain.InferenceResourceSpecs == nil { json.Unmarshal([]byte(setting.InferenceResourceSpecs), &cloudbrain.InferenceResourceSpecs) @@ -708,6 +736,15 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName, jobType models.Jo ctx.Data["resource_type"] = resourceType.Value } } + for _, specialPool := range cloudbrain.SpecialPools.Pools { + + for _, resourceType := range specialPool.Pool { + if resourceType.Queue == jobRes.Config.GpuType { + ctx.Data["resource_type"] = resourceType.Value + } + } + } + } else if task.JobType == string(models.JobTypeInference) { if inferenceGpuInfos == nil { json.Unmarshal([]byte(setting.InferenceGpuTypes), &inferenceGpuInfos) @@ -835,6 +872,8 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName, jobType models.Jo } ctx.Data["task"] = task + labelName := strings.Fields(task.LabelName) + ctx.Data["LabelName"] = labelName ctx.Data["jobName"] = task.JobName ctx.Data["displayJobName"] = task.DisplayJobName version_list_task := make([]*models.Cloudbrain, 0) diff --git a/routers/repo/modelarts.go b/routers/repo/modelarts.go index 188bd5c6c..b5afa4713 100755 --- a/routers/repo/modelarts.go +++ b/routers/repo/modelarts.go @@ -1220,7 +1220,13 @@ func getUserCommand(engineId int, req *modelarts.GenerateTrainJobReq) (string, s userImageUrl := "" userCommand := "" if engineId < 0 { - userCommand = "/bin/bash /home/work/run_train.sh 's3://" + req.CodeObsPath + "' 'code/" + req.BootFile + "' '/tmp/log/train.log' --'data_url'='s3://" + req.DataUrl + "' --'train_url'='s3://" + req.TrainUrl + "'" + tmpCodeObsPath := strings.Trim(req.CodeObsPath, "/") + tmpCodeObsPaths := strings.Split(tmpCodeObsPath, "/") + lastCodeDir := "code" + if len(tmpCodeObsPaths) > 0 { + lastCodeDir = tmpCodeObsPaths[len(tmpCodeObsPaths)-1] + } + userCommand = "/bin/bash /home/work/run_train.sh 's3://" + req.CodeObsPath + "' '" + lastCodeDir + "/" + req.BootFile + "' '/tmp/log/train.log' --'data_url'='s3://" + req.DataUrl + "' --'train_url'='s3://" + req.TrainUrl + "'" var versionInfos modelarts.VersionInfo if err := json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { log.Info("json parse err." + err.Error()) diff --git a/templates/custom/wait_count.tmpl b/templates/custom/wait_count.tmpl index 77c49712d..bef8f1327 100644 --- a/templates/custom/wait_count.tmpl +++ b/templates/custom/wait_count.tmpl @@ -3,13 +3,12 @@ {{$gpuQueue := 0}} {{range $k,$v :=.gpu_types}} {{if eq $k 0}} - {{ $queue := $v.Queue }} + {{ $queue = $v.Queue }} {{ end }} {{ end }} - {{ range $k,$v :=.QueuesDetail }} {{if eq $k $queue}} - {{$gpuQueue :=$v}} + {{$gpuQueue =$v}} {{ end }} {{ end }} {{$queue := ""}} {{$gpuQueue := 0}} - {{range $k,$v :=.gpu_types}} + {{range $k,$v :=.type}} {{if eq $k 0}} - {{ $queue := $v.Queue }} + {{ $queue = $v.Queue }} {{ end }} {{ end }} - {{ range $k,$v :=.QueuesDetail }} + {{ range $k,$v :=.ctx.QueuesDetail }} {{if eq $k $queue}} - {{$gpuQueue :=$v}} + {{$gpuQueue =$v}} {{ end }} {{ end }} - {{.i18n.Tr "repo.wait_count_start"}} - {{if .QueuesDetail}} + {{.ctx.i18n.Tr "repo.wait_count_start"}} + {{if .type}} {{ $gpuQueue }} {{else}} - {{.WaitCount}} + {{.ctx.WaitCount}} {{ end }} - {{.i18n.Tr "repo.wait_count_end"}} diff --git a/templates/repo/cloudbrain/benchmark/new.tmpl b/templates/repo/cloudbrain/benchmark/new.tmpl index 0509ce5bc..fb1296d27 100755 --- a/templates/repo/cloudbrain/benchmark/new.tmpl +++ b/templates/repo/cloudbrain/benchmark/new.tmpl @@ -62,7 +62,7 @@ {{.i18n.Tr "repo.cloudbrain.benchmark.model"}} - {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $ "type" .benchmark_gpu_types}}
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $ "type" .benchmark_gpu_types}}
diff --git a/templates/repo/cloudbrain/benchmark/show.tmpl b/templates/repo/cloudbrain/benchmark/show.tmpl index 59ce3c471..ff4e5e7ea 100755 --- a/templates/repo/cloudbrain/benchmark/show.tmpl +++ b/templates/repo/cloudbrain/benchmark/show.tmpl @@ -232,12 +232,7 @@
- {{if not (eq .StartTime 0)}} - {{TimeSinceUnix1 .StartTime}} - {{else}} - {{TimeSinceUnix1 .CreatedUnix}} - - {{end}} + {{TimeSinceUnix1 .CreatedUnix}} {{$.i18n.Tr "repo.modelarts.status"}}: diff --git a/templates/repo/cloudbrain/inference/new.tmpl b/templates/repo/cloudbrain/inference/new.tmpl index e42c7240a..223fcfe1c 100644 --- a/templates/repo/cloudbrain/inference/new.tmpl +++ b/templates/repo/cloudbrain/inference/new.tmpl @@ -92,7 +92,7 @@ Ascend NPU
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $ "type" .inference_gpu_types}}
diff --git a/templates/repo/cloudbrain/inference/show.tmpl b/templates/repo/cloudbrain/inference/show.tmpl index 7653376dd..00ad25644 100644 --- a/templates/repo/cloudbrain/inference/show.tmpl +++ b/templates/repo/cloudbrain/inference/show.tmpl @@ -266,7 +266,7 @@ onclick="javascript:parseInfo()">{{$.i18n.Tr "repo.cloudbrain.runinfo"}} {{$.i18n.Tr "repo.model_download"}} + data-gpu-flag="true" data-download-flag="{{$.canDownload}}" data-path="{{$.RepoLink}}/cloudbrain/inference-job/{{.JobID}}/result_list" data-version="{{.VersionName}}" data-parents="" data-filename="" data-init="init" >{{$.i18n.Tr "repo.model_download"}}
@@ -317,9 +317,9 @@ {{if not (eq .StartTime 0)}} {{TimeSinceUnix1 .StartTime}} - {{else}} - {{TimeSinceUnix1 .CreatedUnix}} - {{end}} + {{else}} + -- + {{end}}
@@ -420,9 +420,8 @@
- {{if .LabelName}} - {{range $.labelName}} + {{range $.LabelName}} {{.}} {{end}} {{else}} diff --git a/templates/repo/cloudbrain/show.tmpl b/templates/repo/cloudbrain/show.tmpl index 9ae3b9445..e4769c65c 100755 --- a/templates/repo/cloudbrain/show.tmpl +++ b/templates/repo/cloudbrain/show.tmpl @@ -240,12 +240,7 @@
- {{if not (eq .StartTime 0)}} - {{TimeSinceUnix1 .StartTime}} - {{else}} - {{TimeSinceUnix1 .CreatedUnix}} - - {{end}} + {{TimeSinceUnix1 .CreatedUnix}} {{$.i18n.Tr "repo.modelarts.status"}}: @@ -257,9 +252,7 @@ {{$.duration}} - +
@@ -588,33 +581,4 @@ } document.getElementById("info_display").innerHTML = html; } - function stopBubbling(e) { - e = window.event || e; - if (e.stopPropagation) { - e.stopPropagation(); //阻止事件 冒泡传播 - } else { - e.cancelBubble = true; //ie兼容 - } - } - - function refreshStatus(version_name) { - $(".ui.accordion.border-according").each((index, job) => { - const jobID = job.dataset.jobid; - const repoPath = job.dataset.repopath; - const versionname = job.dataset.version - $.get(`/api/v1/repos/${repoPath}/cloudbrain/${jobID}?version_name=${versionname}`, (data) => { - // header status and duration - //$(`#${version_name}-duration-span`).text(data.JobDuration) - $(`#${version_name}-status-span span`).text(data.JobStatus) - $(`#${version_name}-status-span i`).attr("class", data.JobStatus) - // detail status and duration - //$('#'+version_name+'-duration').text(data.JobDuration) - $('#' + version_name + '-status').text(data.JobStatus) - parseLog() - }).fail(function (err) { - console.log(err); - }); - stopBubbling(arguments.callee.caller.arguments[0]) - }) - } \ No newline at end of file diff --git a/templates/repo/cloudbrain/trainjob/new.tmpl b/templates/repo/cloudbrain/trainjob/new.tmpl index 22465492b..c410889b2 100755 --- a/templates/repo/cloudbrain/trainjob/new.tmpl +++ b/templates/repo/cloudbrain/trainjob/new.tmpl @@ -126,7 +126,7 @@ Ascend NPU
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $ "type" .train_gpu_types}}
diff --git a/templates/repo/cloudbrain/trainjob/show.tmpl b/templates/repo/cloudbrain/trainjob/show.tmpl index 2bbad2271..20e5cb55d 100644 --- a/templates/repo/cloudbrain/trainjob/show.tmpl +++ b/templates/repo/cloudbrain/trainjob/show.tmpl @@ -288,7 +288,7 @@ onclick="javascript:parseInfo()">{{$.i18n.Tr "repo.cloudbrain.runinfo"}} {{$.i18n.Tr "repo.modelarts.log"}} - {{$.i18n.Tr "repo.model_download"}} + {{$.i18n.Tr "repo.model_download"}}
@@ -326,8 +326,13 @@
- {{TimeSinceUnix1 .CreatedUnix}} + + {{if not (eq .StartTime 0)}} + {{TimeSinceUnix1 .StartTime}} + {{else}} + -- + {{end}} +
diff --git a/templates/repo/debugjob/index.tmpl b/templates/repo/debugjob/index.tmpl index 14a74ef31..043816e9f 100755 --- a/templates/repo/debugjob/index.tmpl +++ b/templates/repo/debugjob/index.tmpl @@ -386,7 +386,7 @@ {{$.CsrfTokenHtml}} {{if .CanDel}} {{$.i18n.Tr "repo.stop"}} @@ -405,7 +405,7 @@ {{$.CsrfTokenHtml}} {{if .CanDel}} {{$.i18n.Tr "repo.delete"}} diff --git a/templates/repo/grampus/trainjob/gpu/new.tmpl b/templates/repo/grampus/trainjob/gpu/new.tmpl index 2e365fdc6..75b8bcff2 100755 --- a/templates/repo/grampus/trainjob/gpu/new.tmpl +++ b/templates/repo/grampus/trainjob/gpu/new.tmpl @@ -113,7 +113,7 @@ Ascend NPU
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $}}
diff --git a/templates/repo/grampus/trainjob/npu/new.tmpl b/templates/repo/grampus/trainjob/npu/new.tmpl index 1e863ce17..f23942e13 100755 --- a/templates/repo/grampus/trainjob/npu/new.tmpl +++ b/templates/repo/grampus/trainjob/npu/new.tmpl @@ -108,7 +108,7 @@ Ascend NPU
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $}}
diff --git a/templates/repo/grampus/trainjob/show.tmpl b/templates/repo/grampus/trainjob/show.tmpl index 53b20b61a..5d4321736 100755 --- a/templates/repo/grampus/trainjob/show.tmpl +++ b/templates/repo/grampus/trainjob/show.tmpl @@ -236,7 +236,7 @@ {{range $k ,$v := .version_list_task}}
@@ -251,12 +251,7 @@
- - {{if not (eq .StartTime 0)}} - {{TimeSinceUnix1 .StartTime}} - {{else}} - {{TimeSinceUnix1 .CreatedUnix}} - {{end}} + {{TimeSinceUnix1 .CreatedUnix}} {{$.i18n.Tr "repo.modelarts.current_version"}}:{{.VersionName}} @@ -270,9 +265,9 @@ class="cti-mgRight-sm">{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}: {{.TrainJobDuration}} - + + +
@@ -298,7 +293,7 @@ data-tab="first{{$k}}">{{$.i18n.Tr "repo.modelarts.train_job.config"}} {{$.i18n.Tr "repo.modelarts.log"}} - {{$.i18n.Tr "repo.model_download"}} + {{$.i18n.Tr "repo.model_download"}}
@@ -350,7 +345,7 @@ {{if not (eq .StartTime 0)}} {{TimeSinceUnix1 .StartTime}} {{else}} - {{TimeSinceUnix1 .CreatedUnix}} + -- {{end}}
@@ -871,64 +866,7 @@ size = size.toFixed(0);//保留的小数位数 return size + unitArr[index]; } - function refreshStatus(version_name) { - $.get(`/api/v1/repos/${userName}/${repoPath}/grampus/train-job/${jobID}?version_name=${version_name}`, (data) => { - // header status and duration - $(`#${version_name}-duration-span`).text(data.JobDuration) - $(`#${version_name}-status-span span`).text(data.JobStatus) - $(`#${version_name}-status-span i`).attr("class", data.JobStatus) - // detail status and duration - $('#' + version_name + '-duration').text(data.JobDuration) - $('#' + version_name + '-status').text(data.JobStatus) - $('#' + version_name + '-ai_center').text(data.AiCenter) - loadLog(version_name) - - }).fail(function (err) { - console.log(err); - }); - stopBubbling(arguments.callee.caller.arguments[0]) - } - function deleteVersion(version_name) { - stopBubbling(arguments.callee.caller.arguments[0]) - let flag = 1; - $('.ui.basic.modal').modal({ - onDeny: function () { - flag = false - }, - onApprove: function () { - $.post(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/del_version`, { version_name: version_name }, (data) => { - if (data.VersionListCount === 0) { - location.href = `/${userName}/${repoPath}/modelarts/train-job` - } else { - $('#accordion' + version_name).remove() - } - - }).fail(function (err) { - console.log(err); - }); - flag = true - }, - onHidden: function () { - if (flag == false) { - $('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut(); - } - } - }) - .modal('show') - - } - function stopVersion(version_name) { - stopBubbling(arguments.callee.caller.arguments[0]) - $.post(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/stop_version`, { version_name: version_name }, (data) => { - if (data.StatusOK === 0) { - $('#' + version_name + '-stop').addClass('disabled') - refreshStatus(version_name) - } - }).fail(function (err) { - console.log(err); - }); - } function loadLog(version_name) { document.getElementById("mask").style.display = "block" $.get(`/api/v1/repos/${userName}/${repoPath}/grampus/train-job/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => { diff --git a/templates/repo/modelarts/inferencejob/new.tmpl b/templates/repo/modelarts/inferencejob/new.tmpl index 116f53a45..89f4180c0 100644 --- a/templates/repo/modelarts/inferencejob/new.tmpl +++ b/templates/repo/modelarts/inferencejob/new.tmpl @@ -90,7 +90,7 @@ Ascend NPU
- {{template "custom/wait_count_train" .}} + {{template "custom/wait_count_train" Dict "ctx" $}}
diff --git a/templates/repo/modelarts/inferencejob/show.tmpl b/templates/repo/modelarts/inferencejob/show.tmpl index 78a319747..14bb5cf24 100644 --- a/templates/repo/modelarts/inferencejob/show.tmpl +++ b/templates/repo/modelarts/inferencejob/show.tmpl @@ -214,7 +214,7 @@ td, th { {{$.i18n.Tr "repo.modelarts.train_job.config"}} {{$.i18n.Tr "repo.modelarts.log"}} - {{$.i18n.Tr "repo.model_download"}} + {{$.i18n.Tr "repo.model_download"}}
@@ -266,9 +266,9 @@ td, th { {{if not (eq .StartTime 0)}} {{TimeSinceUnix1 .StartTime}} - {{else}} - {{TimeSinceUnix1 .CreatedUnix}} - {{end}} + {{else}} + -- + {{end}}
@@ -451,10 +451,10 @@ td, th { {{template "base/footer" .}}