@@ -72,11 +72,11 @@ type CloudBrainLoginResult struct { | |||
type TaskRole struct { | |||
Name string `json:"name"` | |||
TaskNumber int8 `json:"taskNumber"` | |||
MinSucceededTaskCount int8 `json:"minSucceededTaskCount"` | |||
MinFailedTaskCount int8 `json:"minFailedTaskCount"` | |||
CPUNumber int8 `json:"cpuNumber"` | |||
GPUNumber int8 `json:"gpuNumber"` | |||
TaskNumber int `json:"taskNumber"` | |||
MinSucceededTaskCount int `json:"minSucceededTaskCount"` | |||
MinFailedTaskCount int `json:"minFailedTaskCount"` | |||
CPUNumber int `json:"cpuNumber"` | |||
GPUNumber int `json:"gpuNumber"` | |||
MemoryMB int `json:"memoryMB"` | |||
ShmMB int `json:"shmMB"` | |||
Command string `json:"command"` | |||
@@ -286,6 +286,18 @@ type GpuInfo struct { | |||
Queue string `json:"queue"` | |||
} | |||
type ResourceSpecs struct { | |||
ResourceSpec []*ResourceSpec `json:"resorce_specs"` | |||
} | |||
type ResourceSpec struct { | |||
Id int `json:"id"` | |||
CpuNum int `json:"cpu"` | |||
GpuNum int `json:"gpu"` | |||
MemMiB int `json:"memMiB"` | |||
ShareMemMiB int `json:"shareMemMiB"` | |||
} | |||
type CommitImageParams struct { | |||
Ip string `json:"ip"` | |||
TaskContainerId string `json:"taskContainerId"` | |||
@@ -976,12 +976,12 @@ func CreateUser(u *User) (err error) { | |||
result, err := blockchain.CreateBlockchainAccount() | |||
if err != nil { | |||
log.Error("createBlockchainAccount failed:", err.Error()) | |||
return err | |||
//return err | |||
} else { | |||
u.PublicKey = result.Payload["publickey"].(string) | |||
u.PrivateKey = result.Payload["privatekey"].(string) | |||
} | |||
u.PublicKey = result.Payload["publickey"].(string) | |||
u.PrivateKey = result.Payload["privatekey"].(string) | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
if err = sess.Begin(); err != nil { | |||
@@ -13,6 +13,7 @@ type CreateCloudBrainForm struct { | |||
JobType string `form:"job_type" binding:"Required"` | |||
BenchmarkCategory string `form:"get_benchmark_category"` | |||
GpuType string `form:"gpu_type"` | |||
ResourceSpecId int `form:"resource_spec_id" binding:"Required"` | |||
} | |||
type CommitImageCloudBrainForm struct { | |||
@@ -23,12 +23,29 @@ const ( | |||
Success = "S000" | |||
) | |||
func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue string) error { | |||
var ( | |||
ResourceSpecs *models.ResourceSpecs | |||
) | |||
func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue string, resourceSpecId int) error { | |||
dataActualPath := setting.Attachment.Minio.RealPath + | |||
setting.Attachment.Minio.Bucket + "/" + | |||
setting.Attachment.Minio.BasePath + | |||
models.AttachmentRelativePath(uuid) + | |||
uuid | |||
var resourceSpec *models.ResourceSpec | |||
for _, spec := range ResourceSpecs.ResourceSpec { | |||
if resourceSpecId == spec.Id { | |||
resourceSpec = spec | |||
} | |||
} | |||
if resourceSpec == nil { | |||
log.Error("no such resourceSpecId(%d)", resourceSpecId, ctx.Data["MsgID"]) | |||
return errors.New("no such resourceSpec") | |||
} | |||
jobResult, err := CreateJob(jobName, models.CreateJobParams{ | |||
JobName: jobName, | |||
RetryCount: 1, | |||
@@ -40,10 +57,10 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, | |||
TaskNumber: 1, | |||
MinSucceededTaskCount: 1, | |||
MinFailedTaskCount: 1, | |||
CPUNumber: 2, | |||
GPUNumber: 1, | |||
MemoryMB: 16384, | |||
ShmMB: 8192, | |||
CPUNumber: resourceSpec.CpuNum, | |||
GPUNumber: resourceSpec.GpuNum, | |||
MemoryMB: resourceSpec.MemMiB, | |||
ShmMB: resourceSpec.ShareMemMiB, | |||
Command: command, | |||
NeedIBDevice: false, | |||
IsMainRole: false, | |||
@@ -335,6 +335,7 @@ func ToUser(user *models.User, signed, authed bool) *api.User { | |||
AvatarURL: user.AvatarLink(), | |||
FullName: markup.Sanitize(user.FullName), | |||
Created: user.CreatedUnix.AsTime(), | |||
IsActive: user.IsActive, | |||
} | |||
// hide primary email if API caller is anonymous or user keep email private | |||
if signed && (!user.KeepEmailPrivate || authed) { | |||
@@ -439,6 +439,7 @@ var ( | |||
JobType string | |||
GpuTypes string | |||
DebugServerHost string | |||
ResourceSpecs string | |||
//benchmark config | |||
IsBenchmarkEnabled bool | |||
@@ -1147,7 +1148,8 @@ func NewContext() { | |||
JobPath = sec.Key("JOB_PATH").MustString("/datasets/minio/data/opendata/jobs/") | |||
DebugServerHost = sec.Key("DEBUG_SERVER_HOST").MustString("http://192.168.202.73") | |||
JobType = sec.Key("GPU_TYPE_DEFAULT").MustString("openidebug") | |||
GpuTypes = sec.Key("GPU_TYPES").MustString("openidebug,openidgx") | |||
GpuTypes = sec.Key("GPU_TYPES").MustString("") | |||
ResourceSpecs = sec.Key("RESOURCE_SPECS").MustString("") | |||
sec = Cfg.Section("benchmark") | |||
IsBenchmarkEnabled = sec.Key("ENABLED").MustBool(false) | |||
@@ -26,6 +26,8 @@ type User struct { | |||
Language string `json:"language"` | |||
// Is the user an administrator | |||
IsAdmin bool `json:"is_admin"` | |||
// Is the user active | |||
IsActive bool `json:"is_active"` | |||
// swagger:strfmt date-time | |||
LastLogin time.Time `json:"last_login,omitempty"` | |||
// swagger:strfmt date-time | |||
@@ -90,7 +90,7 @@ loading = Loading… | |||
error404_index = Request forbidden by administrative rules | |||
error500_index = Internal Server Error | |||
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it. | |||
error500= Sorry, the site has encountered some problems, we are trying to <strong>fix the page</strong>, please try again later. | |||
[error] | |||
occurred = An error has occurred | |||
report_message = An error has occurred | |||
@@ -90,6 +90,7 @@ loading=正在加载... | |||
error404_index = 您的访问受限! | |||
error500_index = 抱歉!您指定的网页无法访问。 | |||
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。 | |||
error500=抱歉,站点遇到一些问题,我们正尝试<strong>修复网页</strong>,请您稍后再试。 | |||
[error] | |||
occurred=发生错误 | |||
@@ -1841,6 +1841,14 @@ | |||
"async-done": "^1.2.2" | |||
} | |||
}, | |||
"async-validator": { | |||
"version": "1.8.5", | |||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz", | |||
"integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==", | |||
"requires": { | |||
"babel-runtime": "6.x" | |||
} | |||
}, | |||
"asynckit": { | |||
"version": "0.4.0", | |||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", | |||
@@ -1934,6 +1942,11 @@ | |||
} | |||
} | |||
}, | |||
"babel-helper-vue-jsx-merge-props": { | |||
"version": "2.0.3", | |||
"resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz", | |||
"integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==" | |||
}, | |||
"babel-loader": { | |||
"version": "8.1.0", | |||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", | |||
@@ -3882,6 +3895,26 @@ | |||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.437.tgz", | |||
"integrity": "sha512-PBQn2q68ErqMyBUABh9Gh8R6DunGky8aB5y3N5lPM7OVpldwyUbAK5AX9WcwE/5F6ceqvQ+iQLYkJYRysAs6Bg==" | |||
}, | |||
"element-ui": { | |||
"version": "2.15.5", | |||
"resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.5.tgz", | |||
"integrity": "sha512-B/YCdz2aRY2WnFXzbTRTHPKZHBD/2KV6u88EBnkaARC/Lyxnap+7vpvrcW5UNTyVwjItS5Fj1eQyRy6236lbXg==", | |||
"requires": { | |||
"async-validator": "~1.8.1", | |||
"babel-helper-vue-jsx-merge-props": "^2.0.0", | |||
"deepmerge": "^1.2.0", | |||
"normalize-wheel": "^1.0.1", | |||
"resize-observer-polyfill": "^1.5.0", | |||
"throttle-debounce": "^1.0.1" | |||
}, | |||
"dependencies": { | |||
"deepmerge": { | |||
"version": "1.5.2", | |||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", | |||
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==" | |||
} | |||
} | |||
}, | |||
"elliptic": { | |||
"version": "6.5.4", | |||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", | |||
@@ -9186,6 +9219,11 @@ | |||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", | |||
"integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" | |||
}, | |||
"normalize-wheel": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", | |||
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=" | |||
}, | |||
"now-and-later": { | |||
"version": "2.0.1", | |||
"resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", | |||
@@ -11687,6 +11725,11 @@ | |||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", | |||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" | |||
}, | |||
"resize-observer-polyfill": { | |||
"version": "1.5.1", | |||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", | |||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" | |||
}, | |||
"resolve": { | |||
"version": "1.17.0", | |||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", | |||
@@ -13526,6 +13569,11 @@ | |||
"resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", | |||
"integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" | |||
}, | |||
"throttle-debounce": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-1.1.0.tgz", | |||
"integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==" | |||
}, | |||
"through": { | |||
"version": "2.3.8", | |||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", | |||
@@ -19,6 +19,7 @@ | |||
"cssnano": "4.1.10", | |||
"domino": "2.1.5", | |||
"dropzone": "5.7.2", | |||
"element-ui": "2.15.5", | |||
"esdk-obs-browserjs": "3.20.7", | |||
"esdk-obs-nodejs": "3.20.11", | |||
"fast-glob": "3.2.2", | |||
@@ -148,6 +148,11 @@ func CloudBrainNew(ctx *context.Context) { | |||
json.Unmarshal([]byte(setting.GpuTypes), &gpuInfos) | |||
} | |||
ctx.Data["gpu_types"] = gpuInfos.GpuInfo | |||
if cloudbrain.ResourceSpecs == nil { | |||
json.Unmarshal([]byte(setting.ResourceSpecs), &cloudbrain.ResourceSpecs) | |||
} | |||
ctx.Data["resource_specs"] = cloudbrain.ResourceSpecs.ResourceSpec | |||
ctx.Data["snn4imagenet_path"] = cloudbrain.Snn4imagenetMountPath | |||
ctx.Data["is_snn4imagenet_enabled"] = setting.IsSnn4imagenetEnabled | |||
ctx.HTML(200, tplCloudBrainNew) | |||
@@ -162,6 +167,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
jobType := form.JobType | |||
gpuQueue := setting.JobType | |||
codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath | |||
resourceSpecId := form.ResourceSpecId | |||
if jobType != string(models.JobTypeBenchmark) && jobType != string(models.JobTypeDebug) && jobType != string(models.JobTypeSnn4imagenet) { | |||
log.Error("jobtype error:", jobType, ctx.Data["msgID"]) | |||
@@ -208,7 +214,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
downloadRateCode(repo, jobName, setting.Snn4imagenetCode, snn4imagenetPath, "", "") | |||
} | |||
err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue) | |||
err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue, resourceSpecId) | |||
if err != nil { | |||
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form) | |||
return | |||
@@ -1129,7 +1129,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
//secure api, | |||
m.Group("/secure", func() { | |||
m.Post("/user", binding.Bind(structs.CreateUserOption{}), secure.CreateUser) | |||
m.Post("/user", binding.BindIgnErr(structs.CreateUserOption{}), secure.CreateUser) | |||
}, reqBasicAuth) | |||
m.Group("/api/internal", func() { | |||
@@ -7,6 +7,7 @@ package secure | |||
import ( | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/context" | |||
@@ -75,11 +76,19 @@ func CreateUser(ctx *context.Context, form api.CreateUserOption) { | |||
u.MustChangePassword = *form.MustChangePassword | |||
} | |||
if strings.Contains(form.Email, " ") { | |||
log.Error("CreateUser failed: email(%s) contains blank space", form.Email, ctx.Data["MsgID"]) | |||
ctx.JSON(http.StatusBadRequest, map[string]string{ | |||
"error_msg": "Email contains blank space", | |||
}) | |||
return | |||
} | |||
parseLoginSource(ctx, u, form.SourceID, form.LoginName) | |||
if ctx.Written() { | |||
return | |||
} | |||
if !password.IsComplexEnough(form.Password) { | |||
if !password.IsComplexEnough(form.Password) || len(form.Password) < setting.MinPasswordLength { | |||
log.Error("CreateUser failed: PasswordComplexity", ctx.Data["MsgID"]) | |||
ctx.JSON(http.StatusBadRequest, map[string]string{ | |||
"error_msg": "PasswordComplexity", | |||
@@ -119,6 +128,14 @@ func CreateUser(ctx *context.Context, form api.CreateUserOption) { | |||
return | |||
} | |||
// Send confirmation email | |||
if setting.Service.RegisterEmailConfirm{ | |||
mailer.SendActivateAccountMail(ctx.Locale, u) | |||
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { | |||
log.Error("Set cache(MailResendLimit) fail: %v", err) | |||
} | |||
} | |||
log.Trace("Account created (%s): %s", ctx.User.Name, u.Name, ctx.Data["MsgID"]) | |||
// Send email notification. | |||
@@ -127,7 +127,7 @@ | |||
<img src="/img/i-pic-01.svg"> | |||
</div> | |||
<div class="content"> | |||
<a class="ui centered header">开发要素统一管理</a> | |||
<h3 class="ui centered header">开发要素统一管理</h3> | |||
<div class="description"> | |||
平台提供了AI开发四大要素:模型代码、数据集、模型和执行环境的统一管理 | |||
</div> | |||
@@ -138,7 +138,7 @@ | |||
<img src="/img/i-pic-02.svg"> | |||
</div> | |||
<div class="content"> | |||
<a class="ui centered header">数据协同与共享</a> | |||
<h3 class="ui centered header">数据协同与共享</h3> | |||
<div class="description"> | |||
通过在项目中上传数据集,项目成员多人协作完成数据预处理;也可以通过将数据设置为公有数据集,与社区开发者共同建立更好的模型 | |||
</div> | |||
@@ -149,7 +149,7 @@ | |||
<img src="/img/i-pic-03.svg"> | |||
</div> | |||
<div class="content"> | |||
<a class="ui centered header">模型管理与共享</a> | |||
<h3 class="ui centered header">模型管理与共享</h3> | |||
<div class="description"> | |||
将模型与代码版本建立关联,可以基于代码历史版本,使用不同的方式调整模型,并将结果保存下来;训练好的模型可以开放共享,让更多人的使用模型测试并提出反馈 | |||
</div> | |||
@@ -160,7 +160,7 @@ | |||
<img src="/img/i-pic-04.svg"> | |||
</div> | |||
<div class="content"> | |||
<a class="ui centered header">一次配置,多次使用</a> | |||
<h3 class="ui centered header">一次配置,多次使用</h3> | |||
<div class="description"> | |||
提供执行环境共享,一次配置,多次使用,降低模型开发门槛,避免花费重复的时间配置复杂的环境 | |||
</div> | |||
@@ -161,14 +161,15 @@ | |||
<div class="inline required field"> | |||
<label>镜像</label> | |||
<select class="ui search" id="cloudbrain_image" placeholder="选择镜像" style='width:385px;' name="image"> | |||
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image"> | |||
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image"> | |||
{{range .images}} | |||
<option name="image" value="{{.Place}}">{{.PlaceView}}</option> | |||
{{end}} | |||
{{range .public_images}} | |||
<option name="image" value="{{.Place}}">{{.PlaceView}}</option> | |||
{{end}} | |||
</select> | |||
</datalist> | |||
</div> | |||
<div class="inline required field"> | |||
@@ -180,6 +181,15 @@ | |||
</select> | |||
</div> | |||
<div class="inline required field"> | |||
<label>资源规格</label> | |||
<select id="cloudbrain_resource_spec" class="ui search dropdown" placeholder="选择资源规格" style='width:385px' name="resource_spec_id"> | |||
{{range .resource_specs}} | |||
<option name="resource_spec_id" value="{{.Id}}">GPU数:{{.GpuNum}},CPU数:{{.CpuNum}},内存(MB):{{.MemMiB}},共享内存(MB):{{.ShareMemMiB}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<div class="inline required field"> | |||
<label>数据集存放路径</label> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" autofocus required maxlength="255" readonly="readonly"> | |||
@@ -242,9 +252,9 @@ | |||
.dropdown(); | |||
$('#cloudbrain_image').select2({ | |||
placeholder: "选择镜像" | |||
}); | |||
// $('#cloudbrain_image').select2({ | |||
// placeholder: "选择镜像" | |||
// }); | |||
$(".ui.button.reset").click(function(e){ | |||
@@ -12,6 +12,15 @@ | |||
float: left; | |||
margin: .25em; | |||
} | |||
.edit-link{ | |||
vertical-align: top; | |||
display: inline-block; | |||
overflow: hidden; | |||
word-break: keep-all; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
width: 16.5em; | |||
} | |||
#contributorInfo > a.circular{ | |||
height: 2.0em; | |||
padding: 0; | |||
@@ -197,37 +206,55 @@ | |||
</div> | |||
<div class="ui six wide tablet four wide computer column"> | |||
<div id="repo-desc"> | |||
<h4 class="ui header">简介</h4> | |||
<h4 id="about-desc" class="ui header">简介 | |||
<!-- <a class="edit-icon" href="javascript:void(0)"> | |||
<i class="gray edit outline icon"></i> | |||
</a> --> | |||
</h4> | |||
<p> | |||
{{if .Repository.DescriptionHTML}} | |||
<span class="description">{{.Repository.DescriptionHTML}}</span> | |||
{{else}} | |||
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span> | |||
{{end}} | |||
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | |||
{{if .Repository.DescriptionHTML}} | |||
<span class="description" style="word-break:break-all">{{.Repository.DescriptionHTML}}</span> | |||
{{else}} | |||
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span> | |||
{{end}} | |||
</p> | |||
</div> | |||
{{if .Repository.Website}} | |||
<p class="ui"> | |||
<i class="gray linkify icon"></i> | |||
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | |||
</p> | |||
{{end}} | |||
<p class="ui" id="repo-topics"> | |||
<i class="grey bookmark icon"></i> | |||
{{range .Topics}}<a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}} | |||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<a id="manage_topic">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}} | |||
</p> | |||
<p class="ui"> | |||
<i class="grey code icon"></i> | |||
{{range .LanguageStats}} | |||
{{.Language}} | |||
{{end}} | |||
</p> | |||
{{if .LICENSE}} | |||
<p class="ui"> | |||
<i class="grey clone icon"></i> | |||
{{if .LICENSE}} | |||
{{.LICENSE}} | |||
{{end}} | |||
{{.LICENSE}} | |||
</p> | |||
{{end}} | |||
<div class="ui divider"></div> | |||
@@ -6,7 +6,7 @@ | |||
<img class="ui centered medium image" src="{{StaticUrlPrefix}}/img/icon-500@2x.png"> | |||
<h2>{{.i18n.Tr "error500_index"}}</h2> | |||
<p>{{.i18n.Tr "error404" | Safe}}</p> | |||
<p>{{.i18n.Tr "error500" | Safe}}</p> | |||
</div> | |||
</div> | |||
@@ -23,7 +23,7 @@ | |||
</div> | |||
<div class="required field {{if .Err_Email}}error{{end}}"> | |||
<label for="email">{{.i18n.Tr "email"}}</label> | |||
<input id="email" name="email" value="{{.SignedUser.Email}}"> | |||
<input id="email" name="email" disabled value="{{.SignedUser.Email}}"> | |||
</div> | |||
<div class="inline field"> | |||
<div class="ui checkbox" id="keep-email-private"> | |||
@@ -0,0 +1,144 @@ | |||
<template> | |||
<div> | |||
<h4 id="about-desc" class="ui header">简介 | |||
<!-- <a class="edit-icon" href="javascript:void(0)" @click="editClick"> | |||
<i class="gray edit outline icon"></i> | |||
</a> --> | |||
</h4> | |||
<edit-dialog-cmpt | |||
:vmContext="vmContext" | |||
dialogTitle="编辑仓库信息" | |||
v-model="editDataDialog" | |||
:deleteCallback="editDataFunc" | |||
:deleteLoading ="editDataListLoading" | |||
deleteParam = "ruleForm" | |||
@input="initForm" | |||
> | |||
<div slot="title"> | |||
</div> | |||
<div slot="content"> | |||
<el-form label-position="top" :model="info" :rules="rule" ref="ruleForm"> | |||
<el-form-item label="简介" prop="desc"> | |||
<el-input v-model="info.desc" type="textarea" :autosize="{minRows:2,maxRows:6}"></el-input> | |||
</el-form-item> | |||
<el-form-item label="主页" prop="index_web" > | |||
<el-input v-model="info.index_web" placeholder="主页(eg: https://git.openi.org.cn)"></el-input> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
</edit-dialog-cmpt> | |||
</div> | |||
</template> | |||
<script> | |||
const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config; | |||
import editDialogCmpt from './basic/editDialog.vue'; | |||
export default { | |||
components: { | |||
editDialogCmpt, | |||
}, | |||
data() { | |||
return { | |||
vmContext: this, | |||
editDataDialog: false, | |||
editDataListLoading: false, | |||
url: '', | |||
info: { | |||
desc: '', | |||
index_web: '', | |||
repo_name_name: '', | |||
}, | |||
// rule1:[{min:3,max:5,message:'1',trigger:"blur"}], | |||
rule: { | |||
index_web: [ | |||
{required: false, pattern: /(^$)|(^(http|https):\/\/(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).*)|(^(http|https):\/\/[a-zA-Z0-9]+([_\-\.][a-zA-Z0-9]+)*\.[a-zA-Z]{2,10}(:[0-9]{1,10})?(\?.*)?(\/.*)?$)/,message:'请输入有效的URL',tigger:['change','blur']} | |||
] | |||
} | |||
}; | |||
}, | |||
methods: { | |||
editClick() { | |||
this.editDataDialog = true; | |||
}, | |||
getDesc() { | |||
const el = $('span.description').text(); | |||
this.info.desc = el; | |||
}, | |||
getWeb() { | |||
const el = $('a.link').text(); | |||
this.info.index_web = el; | |||
}, | |||
getRepoName() { | |||
const el = this.url.split('/')[2]; | |||
this.info.repo_name = el; | |||
}, | |||
initForm(diaolog) { | |||
if (diaolog === false) { | |||
console.log("--watch----------") | |||
this.getRepoName(); | |||
this.getDesc(); | |||
this.getWeb(); | |||
} | |||
}, | |||
editDataFunc(formName) { | |||
this.$refs[formName].validate((valid)=>{ | |||
if (valid) { | |||
this.$axios({ | |||
method: 'post', | |||
url: this.url, | |||
header: {'content-type': 'application/x-www-form-urlencoded'}, | |||
data: this.qs.stringify({ | |||
_csrf: csrf, | |||
action: 'update', | |||
repo_name: this.info.repo_name, | |||
description: this.info.desc, | |||
website: this.info.index_web | |||
}) | |||
}).then((res) => { | |||
location.reload(); | |||
this.editDataDialog = false; | |||
}).catch((error) => { | |||
this.editDataDialog = false; | |||
}) | |||
} | |||
else { | |||
return false; | |||
} | |||
}) | |||
}, | |||
getUrl() { | |||
const url = `${window.location.pathname}/settings`; | |||
this.url = url; | |||
} | |||
}, | |||
mounted() { | |||
this.getUrl(); | |||
this.getRepoName(); | |||
this.getDesc(); | |||
this.getWeb(); | |||
}, | |||
created() { | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
.edit-icon{ | |||
float: right; | |||
font-size: 16px; | |||
display: block; | |||
top: -2px; | |||
color: #8c92a4; | |||
background-color: transparent; | |||
} | |||
</style> |
@@ -0,0 +1,105 @@ | |||
<template> | |||
<el-dialog :close-on-click-modal="!deleteLoading" v-dlg-drag :title="dialogTitle" :visible.sync="deleteDialog"> | |||
<div class="message-box__content"> | |||
<div class="message-box-title" > | |||
<slot name="title"></slot> | |||
</div> | |||
<div class="message-box-info"> | |||
<slot name="info"></slot> | |||
</div> | |||
</div> | |||
<slot name="content"></slot> | |||
<div slot="footer" class="dialog-footer"> | |||
<el-button @click="deleteDialog = false">{{"取消"}}</el-button> | |||
<el-button type="primary" @click="deleteCallback.call(vmContext,deleteParam)">{{"确定"}}</el-button> | |||
</div> | |||
</el-dialog> | |||
</template> | |||
<script type="text/javascript"> | |||
export default { | |||
data() { | |||
return { | |||
deleteDialog: false, | |||
}; | |||
}, | |||
props: { | |||
vmContext: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
dialogTitle: { | |||
type: String, | |||
default: '', | |||
}, | |||
deleteLoading: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
deleteCallback: { | |||
type: Function, | |||
default() { | |||
return () => {}; | |||
} | |||
}, | |||
deleteParam: { | |||
type: String, | |||
default: '' | |||
}, | |||
value: { | |||
type: Boolean, | |||
default: false, | |||
} | |||
}, | |||
computed: { | |||
}, | |||
watch: { | |||
deleteDialog() { | |||
this.$emit('input', this.deleteDialog); | |||
}, | |||
value() { | |||
this.deleteDialog = this.value; | |||
}, | |||
}, | |||
created() { | |||
this.deleteDialog = this.value; | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
.el-message-box__content .icon{float:left;} | |||
.message-box__content{} | |||
.message-box__content .icon{float:left;margin-right:20px;} | |||
.message-box__content .message-box-title{font-size:16px;padding:2px 0;color:#333;} | |||
.message-box__content .message-box-p{font-size:16px;padding:20px 0;color:#333;} | |||
.message-box__content .message-box-info{color:#999;padding:2px 0;} | |||
.dialog-footer,.el-message-box__btns{text-align:center;} | |||
/deep/ .el-dialog__header { | |||
background: #0067b3; | |||
padding: 12px 30px; | |||
line-height: 25px;} | |||
/deep/ .el-dialog__title { | |||
font-family: PingFangSC-Regular; | |||
font-size: 18px; | |||
color: #fff; | |||
font-weight: 200; | |||
line-height: 25px; | |||
height: 25px; | |||
} | |||
/deep/ .el-dialog__footer { | |||
background: #eff3f9; | |||
padding: 20px 30px; | |||
} | |||
/deep/ .el-dialog{ | |||
width: 40%; | |||
} | |||
</style> |
@@ -6,6 +6,10 @@ import './publicpath.js'; | |||
import './polyfills.js'; | |||
import Vue from 'vue'; | |||
import ElementUI from 'element-ui'; | |||
import 'element-ui/lib/theme-chalk/index.css'; | |||
import axios from 'axios'; | |||
import qs from 'qs'; | |||
import 'jquery.are-you-sure'; | |||
import './vendor/semanticdropdown.js'; | |||
import {svg} from './utils.js'; | |||
@@ -29,8 +33,12 @@ import { | |||
} from './features/notification.js'; | |||
import {createCodeEditor} from './features/codeeditor.js'; | |||
import MinioUploader from './components/MinioUploader.vue'; | |||
import ObsUploader from './components/ObsUploader.vue' | |||
import ObsUploader from './components/ObsUploader.vue'; | |||
import EditAboutInfo from './components/EditAboutInfo.vue'; | |||
Vue.use(ElementUI); | |||
Vue.prototype.$axios = axios; | |||
Vue.prototype.qs = qs; | |||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||
function htmlEncode(text) { | |||
@@ -2957,6 +2965,7 @@ $(document).ready(async () => { | |||
initVueApp(); | |||
initVueUploader(); | |||
initObsUploader(); | |||
initVueEditAbout(); | |||
initTeamSettings(); | |||
initCtrlEnterSubmit(); | |||
initNavbarContentToggle(); | |||
@@ -3642,6 +3651,19 @@ function initVueUploader() { | |||
}); | |||
} | |||
function initVueEditAbout() { | |||
const el = document.getElementById('about-desc'); | |||
console.log(el) | |||
if (!el) { | |||
return; | |||
} | |||
new Vue({ | |||
el: '#about-desc', | |||
render: h => h(EditAboutInfo) | |||
}); | |||
} | |||
// 新增 | |||
function initObsUploader() { | |||
const el = document.getElementById('obsUploader'); | |||