mirror of
https://github.com/gogs/gogs.git
synced 2026-07-18 23:11:30 +02:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e61ec316e | ||
|
|
13e35398aa | ||
|
|
9f4e584122 | ||
|
|
d5c6b53571 | ||
|
|
f0ca16d78f | ||
|
|
9b0858b1ad | ||
|
|
c73e9057ae | ||
|
|
fd70f9ec1b | ||
|
|
0b56272c13 | ||
|
|
677b1ec627 | ||
|
|
3ea1443885 | ||
|
|
b6272d1803 | ||
|
|
40de2f78c4 | ||
|
|
aa2148a7a9 | ||
|
|
bd555551ce | ||
|
|
e1c5008238 | ||
|
|
0f1ff9b1ad | ||
|
|
52cc58fd9d | ||
|
|
477bea574a | ||
|
|
37566f71a9 | ||
|
|
afdb0c7f9d | ||
|
|
63c1f9a23f | ||
|
|
e948c7c262 | ||
|
|
81a44e4cd0 | ||
|
|
f059866a21 | ||
|
|
234a7c19a4 | ||
|
|
35dd41c3a2 | ||
|
|
afc659442d | ||
|
|
6a6636d451 | ||
|
|
46742a79ca | ||
|
|
d61def86e1 | ||
|
|
30c750a5df | ||
|
|
bae1d65564 | ||
|
|
995a805a31 | ||
|
|
c0cfd62b90 | ||
|
|
ebbe6177a9 | ||
|
|
97b39ae2e4 | ||
|
|
0f77ad219c | ||
|
|
8b31be43c6 | ||
|
|
21dbcb7c77 | ||
|
|
0d7bb9af46 | ||
|
|
86e9ebdcc9 | ||
|
|
350e0080e6 | ||
|
|
66e2016eeb | ||
|
|
030b3d751e | ||
|
|
79537467da | ||
|
|
a18decf4cc | ||
|
|
20b5c23a19 | ||
|
|
e6e2cf7855 | ||
|
|
c884ecfea1 | ||
|
|
edbe1de026 | ||
|
|
2321b4b272 | ||
|
|
007cf33e88 | ||
|
|
b231b8c927 | ||
|
|
d01e7b0173 | ||
|
|
bb267e30b6 | ||
|
|
eca42bcb44 | ||
|
|
5ffeca35e7 | ||
|
|
87be137b88 | ||
|
|
0a697517ac | ||
|
|
c4820f119d | ||
|
|
57b3be4016 | ||
|
|
b033f2f535 | ||
|
|
f34b04cfc0 | ||
|
|
ec71d538fc | ||
|
|
6919c80f0b | ||
|
|
99599c099f | ||
|
|
d01f2f3c22 | ||
|
|
1a5aa5e0c0 | ||
|
|
9803c421f5 | ||
|
|
792ec63c8a |
34
.gopmfile
34
.gopmfile
@@ -3,31 +3,31 @@ path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/beego/memcache = commit:2aea774416
|
||||
github.com/beego/redigo = commit:856744a0d5
|
||||
github.com/Unknwon/cae = commit:2e70a1351b
|
||||
github.com/Unknwon/com = commit:2cbcbc6916
|
||||
github.com/Unknwon/goconfig = commit:0f8d8dc1c0
|
||||
github.com/Unknwon/i18n = commit:aec5f77857
|
||||
github.com/Unknwon/macaron = commit:3d307c1232
|
||||
github.com/codegangsta/cli = commit:7381bc4e62
|
||||
github.com/go-sql-driver/mysql = commit:8111ee3ec3
|
||||
github.com/go-xorm/core = commit:3e0fa232ab
|
||||
github.com/go-xorm/xorm = commit:58d33844ce
|
||||
github.com/Unknwon/com = commit:d9bcf409c8
|
||||
github.com/Unknwon/i18n = commit:1e88666229
|
||||
github.com/Unknwon/macaron = commit:256ba0beab
|
||||
github.com/codegangsta/cli = commit:a14c5b47c7
|
||||
github.com/go-sql-driver/mysql = commit:04cf947760
|
||||
github.com/go-xorm/core = commit:e7882d8b00
|
||||
github.com/go-xorm/xorm = commit:dcc529b68a
|
||||
github.com/gogits/go-gogs-client = commit:92e76d616a
|
||||
github.com/gogits/oauth2 = commit:99cbec870a
|
||||
github.com/lib/pq = commit:b021d0ef20
|
||||
github.com/macaron-contrib/binding = commit:0e23661e7d
|
||||
github.com/macaron-contrib/cache = commit:0bb9e6c9ef
|
||||
github.com/lib/pq = commit:3e3efe51a0
|
||||
github.com/macaron-contrib/binding = commit:0fbe4b9707
|
||||
github.com/macaron-contrib/cache = commit:1d99a5b621
|
||||
github.com/macaron-contrib/captcha = commit:3567dc48b8
|
||||
github.com/macaron-contrib/csrf = commit:422b79675c
|
||||
github.com/macaron-contrib/i18n = commit:184bb832f2
|
||||
github.com/macaron-contrib/csrf = commit:3ea14e7ee7
|
||||
github.com/macaron-contrib/i18n = commit:0ee0539c84
|
||||
github.com/macaron-contrib/oauth2 = commit:8f394c3629
|
||||
github.com/macaron-contrib/session = commit:f00d48fd4f
|
||||
github.com/macaron-contrib/session = commit:2633cef14f
|
||||
github.com/macaron-contrib/toolbox = commit:57127bcc89
|
||||
github.com/mattn/go-sqlite3 = commit:a80c27ba33
|
||||
github.com/nfnt/resize = commit:581d15cb53
|
||||
github.com/nfnt/resize = commit:8f44931448
|
||||
github.com/russross/blackfriday = commit:05b8cefd6a
|
||||
github.com/shurcooL/go = commit:48293cbc7a
|
||||
github.com/saintfish/chardet = commit:3af4cd4741
|
||||
gopkg.in/ini.v1 = commit:28ad8c408b
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
|
||||
[res]
|
||||
include = conf|etc|public|scripts|templates
|
||||
|
||||
@@ -2,4 +2,6 @@ language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.3
|
||||
|
||||
sudo: false
|
||||
|
||||
@@ -64,8 +64,17 @@ Before open any new issue, please check your problem on [Troubleshooting](http:/
|
||||
|
||||
Please take a moment to check that an issue on [GitHub](https://github.com/gogits/gogs/issues) or card on [Trello](https://trello.com/b/uxAoeLUl/gogs-go-git-service) doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
|
||||
### Discuss your design on the mailing list
|
||||
## Code of conduct
|
||||
|
||||
We recommend discussing your plans [on the mailing list](https://groups.google.com/forum/#!forum/gogits) before starting to code - especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give feedback on your design, and maybe point out if someone else is working on the same thing.
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We may close your pull request if not first discussed on the mailing list. We aren't doing this to be jerks. We are doing this to prevent people from spending large amounts of time on changes that may need to be designed or architected in a specific way, or may not align with the vision of the project.
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
||||
reported by emailing contact@gitlab.com
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
||||
@@ -1,11 +1,13 @@
|
||||
Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://travis-ci.org/gogits/gogs)
|
||||
=====================
|
||||
|
||||
[](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
|
||||
|
||||

|
||||
|
||||
##### Current version: 0.5.8 Beta
|
||||
##### Current version: 0.5.11 Beta
|
||||
|
||||
### NOTICES
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
|
||||
|
||||

|
||||
|
||||
##### 当前版本:0.5.8 Beta
|
||||
##### 当前版本:0.5.11 Beta
|
||||
|
||||
## 开发目的
|
||||
|
||||
|
||||
46
cmd/web.go
46
cmd/web.go
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/macaron-contrib/oauth2"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/macaron-contrib/toolbox"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
@@ -55,6 +56,12 @@ and it takes care of all the other things for you`,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
type VerChecker struct {
|
||||
ImportPath string
|
||||
Version func() string
|
||||
Expected string
|
||||
}
|
||||
|
||||
// checkVersion checks if binary matches the version of templates files.
|
||||
func checkVersion() {
|
||||
// Templates.
|
||||
@@ -67,17 +74,20 @@ func checkVersion() {
|
||||
}
|
||||
|
||||
// Check dependency version.
|
||||
macaronVer := git.MustParseVersion(strings.Join(strings.Split(macaron.Version(), ".")[:3], "."))
|
||||
if macaronVer.LessThan(git.MustParseVersion("0.4.7")) {
|
||||
log.Fatal(4, "Package macaron version is too old, did you forget to update?(github.com/Unknwon/macaron)")
|
||||
checkers := []VerChecker{
|
||||
{"github.com/Unknwon/macaron", macaron.Version, "0.4.9"},
|
||||
{"github.com/macaron-contrib/binding", binding.Version, "0.0.4"},
|
||||
{"github.com/macaron-contrib/cache", cache.Version, "0.0.5"},
|
||||
{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.1"},
|
||||
{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.5"},
|
||||
{"github.com/macaron-contrib/session", session.Version, "0.1.4"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.0.1"},
|
||||
}
|
||||
i18nVer := git.MustParseVersion(i18n.Version())
|
||||
if i18nVer.LessThan(git.MustParseVersion("0.0.2")) {
|
||||
log.Fatal(4, "Package i18n version is too old, did you forget to update?(github.com/macaron-contrib/i18n)")
|
||||
}
|
||||
sessionVer := git.MustParseVersion(session.Version())
|
||||
if sessionVer.LessThan(git.MustParseVersion("0.0.5")) {
|
||||
log.Fatal(4, "Package session version is too old, did you forget to update?(github.com/macaron-contrib/session)")
|
||||
for _, c := range checkers {
|
||||
ver := strings.Join(strings.Split(c.Version(), ".")[:3], ".")
|
||||
if git.MustParseVersion(ver).LessThan(git.MustParseVersion(c.Expected)) {
|
||||
log.Fatal(4, "Package '%s' version is too old(%s -> %s), did you forget to update?", c.ImportPath, ver, c.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,18 +129,15 @@ func newMacaron() *macaron.Macaron {
|
||||
Redirect: true,
|
||||
}))
|
||||
m.Use(cache.Cacher(cache.Options{
|
||||
Adapter: setting.CacheAdapter,
|
||||
Interval: setting.CacheInternal,
|
||||
Conn: setting.CacheConn,
|
||||
Adapter: setting.CacheAdapter,
|
||||
AdapterConfig: setting.CacheConn,
|
||||
Interval: setting.CacheInternal,
|
||||
}))
|
||||
m.Use(captcha.Captchaer(captcha.Options{
|
||||
SubURL: setting.AppSubUrl,
|
||||
}))
|
||||
m.Use(session.Sessioner(session.Options{
|
||||
Provider: setting.SessionProvider,
|
||||
Config: *setting.SessionConfig,
|
||||
}))
|
||||
m.Use(csrf.Generate(csrf.Options{
|
||||
m.Use(session.Sessioner(setting.SessionConfig))
|
||||
m.Use(csrf.Csrfer(csrf.Options{
|
||||
Secret: setting.SecretKey,
|
||||
SetCookie: true,
|
||||
Header: "X-Csrf-Token",
|
||||
@@ -235,6 +242,8 @@ func runWeb(*cli.Context) {
|
||||
m.Get("", user.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
|
||||
m.Get("/email", user.SettingsEmails)
|
||||
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
m.Get("/ssh", user.SettingsSSHKeys)
|
||||
@@ -246,6 +255,7 @@ func runWeb(*cli.Context) {
|
||||
m.Group("/user", func() {
|
||||
// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
|
||||
m.Any("/activate", user.Activate)
|
||||
m.Any("/activate_email", user.ActivateEmail)
|
||||
m.Get("/email2user", user.Email2User)
|
||||
m.Get("/forget_password", user.ForgotPasswd)
|
||||
m.Post("/forget_password", user.ForgotPasswdPost)
|
||||
|
||||
21
conf/app.ini
21
conf/app.ini
@@ -94,8 +94,11 @@ SUBJECT = %(APP_NAME)s
|
||||
; Mail server
|
||||
; Gmail: smtp.gmail.com:587
|
||||
; QQ: smtp.qq.com:25
|
||||
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
|
||||
HOST =
|
||||
; Mail from address
|
||||
; Do not verify the certificate of the server. Only use this for self-signed certificates
|
||||
SKIP_VERIFY =
|
||||
; Mail from address, RFC 5322. This can be just an email address, or the "Name" <email@example.com> format
|
||||
FROM =
|
||||
; Mailer user name and password
|
||||
USER =
|
||||
@@ -151,7 +154,7 @@ HOST =
|
||||
|
||||
[session]
|
||||
; Either "memory", "file", "redis" or "mysql", default is "memory"
|
||||
PROVIDER = file
|
||||
PROVIDER = memory
|
||||
; Provider config options
|
||||
; memory: not have any config yet
|
||||
; file: session file path, e.g. `data/sessions`
|
||||
@@ -257,14 +260,20 @@ DRIVER =
|
||||
CONN =
|
||||
|
||||
[git]
|
||||
MAX_GITDIFF_LINES = 10000
|
||||
; Arguments for command 'git fsck', e.g.: "--unreachable --tags"
|
||||
; see more on http://git-scm.com/docs/git-fsck/1.7.5
|
||||
FSCK_ARGS =
|
||||
MAX_GIT_DIFF_LINES = 10000
|
||||
; Arguments for command 'git gc', e.g.: "--aggressive --auto"
|
||||
; see more on http://git-scm.com/docs/git-gc/1.7.5
|
||||
GC_ARGS =
|
||||
|
||||
; Git health check.
|
||||
[git.fsck]
|
||||
ENABLE = true
|
||||
; Execution interval in hours. Default is 24.
|
||||
INTERVAL = 24
|
||||
; Arguments for command 'git fsck', e.g.: "--unreachable --tags"
|
||||
; see more on http://git-scm.com/docs/git-fsck/1.7.5
|
||||
ARGS =
|
||||
|
||||
[i18n]
|
||||
LANGS = en-US,zh-CN,zh-HK,de-DE,fr-CA,nl-NL,lv-LV
|
||||
NAMES = English,简体中文,繁體中文,Deutsch,Français,Nederlands,Latviešu
|
||||
|
||||
@@ -61,7 +61,7 @@ domain = Domain
|
||||
domain_helper = This affects SSH clone URLs.
|
||||
app_url = Application URL
|
||||
app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in e-mail.
|
||||
email_title = Email Service Settings(Optional)
|
||||
email_title = E-mail Service Settings(Optional)
|
||||
smtp_host = SMTP Host
|
||||
mailer_user = Sender E-mail
|
||||
mailer_password = Sender Password
|
||||
@@ -109,7 +109,7 @@ confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s
|
||||
sign_in_email = Sign in to your e-mail
|
||||
active_your_account = Activate Your Account
|
||||
resent_limit_prompt = Sorry, you are sending an activation e-mail too frequently. Please wait 3 minutes.
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address(<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed e-mail address(<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
|
||||
resend_mail = Click here to resend your activation e-mail
|
||||
email_not_associate = This e-mail address does not associate to any account.
|
||||
send_reset_mail = Click here to (re)send your password reset e-mail
|
||||
@@ -192,7 +192,7 @@ delete = Delete Account
|
||||
uid = Uid
|
||||
|
||||
public_profile = Public Profile
|
||||
profile_desc = Your Email address is public and will be used for any account related notifications, and any web based operations made via the site.
|
||||
profile_desc = Your E-mail address is public and will be used for any account related notifications, and any web based operations made via the site.
|
||||
full_name = Full Name
|
||||
website = Website
|
||||
location = Location
|
||||
@@ -217,6 +217,15 @@ new_password = New Password
|
||||
password_incorrect = Current password is not correct.
|
||||
change_password_success = Password is changed successfully. You can now sign in via new password.
|
||||
|
||||
emails = E-mail Addresses
|
||||
manage_emails = Manage e-mail addresses
|
||||
email_desc = Current e-mail addresses
|
||||
primary = Primary
|
||||
primary_email = Make primary
|
||||
delete_email = Delete
|
||||
add_new_email = Add new e-mail address
|
||||
add_email = Add e-mail
|
||||
|
||||
manage_ssh_keys = Manage SSH Keys
|
||||
add_key = Add Key
|
||||
ssh_desc = This is a list of SSH keys associated with your account. Remove any keys that you do not recognize.
|
||||
@@ -404,7 +413,7 @@ release.tag_name_already_exist = Release with this tag name has already existed.
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_name_helper = Great organization names are short and memorable.
|
||||
org_email_helper = Organization's Email receives all notifications and confirmations.
|
||||
org_email_helper = Organization's E-mail receives all notifications and confirmations.
|
||||
create_org = Create Organization
|
||||
repo_updated = Updated
|
||||
people = People
|
||||
@@ -619,7 +628,7 @@ config.db_ssl_mode_helper = (for "postgres" only)
|
||||
config.db_path = Path
|
||||
config.db_path_helper = (for "sqlite3" only)
|
||||
config.service_config = Service Configuration
|
||||
config.register_email_confirm = Register Email Confirmation
|
||||
config.register_email_confirm = Register E-mail Confirmation
|
||||
config.disable_register = Disable Registration
|
||||
config.require_sign_in_view = Require Sign In View
|
||||
config.mail_notify = Mail Notification
|
||||
|
||||
@@ -26,7 +26,7 @@ organization=Organisation
|
||||
mirror=Miroir
|
||||
new_repo=Nouveau Référentiel
|
||||
new_migrate=Nouvelle Migration
|
||||
new_fork=Nouveau référentiel de fourche
|
||||
new_fork=Nouveau Référentiel d'Embranchement
|
||||
new_org=Nouvelle Organisation
|
||||
manage_org=Gérer les Organisations
|
||||
admin_panel=Administration
|
||||
@@ -257,9 +257,9 @@ repo_name=Nom du Référentiel
|
||||
repo_name_helper=Idéalement, le nom d'un Référentiel devrait être court, mémorable et <strong>unique</strong>.
|
||||
visibility=Visibilité
|
||||
visiblity_helper=Ce Référentiel est <span class="label label-red label-radius">Privé</span>
|
||||
fork_repo=Référentiel de la fourche
|
||||
fork_from=Fourche de
|
||||
fork_visiblity_helper=Référentiel Aristide ne peut pas changer sa visiblité
|
||||
fork_repo=Référentiel d'Embranchement
|
||||
fork_from=Embranchement de
|
||||
fork_visiblity_helper=Un Référentiel d'embranchement ne peut pas changer sa visiblité
|
||||
repo_desc=Description
|
||||
repo_lang=Langue
|
||||
repo_lang_helper=Sélectionner un fichier .gitignore
|
||||
@@ -377,29 +377,29 @@ diff.stats_desc=<strong> %d fichiers modifiés</strong> avec <strong>%d ajouts</
|
||||
diff.bin=BIN
|
||||
diff.view_file=Voir le fichier
|
||||
|
||||
release.releases=Releases
|
||||
release.new_release=New Release
|
||||
release.draft=Draft
|
||||
release.prerelease=Pre-Release
|
||||
release.releases=Versions
|
||||
release.new_release=Nouvelle version
|
||||
release.draft=Brouillon
|
||||
release.prerelease=Pré-publication
|
||||
release.stable=Stable
|
||||
release.edit=edit
|
||||
release.ahead=<strong>%d</strong> commits to %s since this release
|
||||
release.source_code=Source Code
|
||||
release.tag_name=Tag name
|
||||
release.target=Target
|
||||
release.tag_helper=Choose an existing tag, or create a new tag on publish.
|
||||
release.release_title=Release title
|
||||
release.content_with_md=Content with <a href="%s">Markdown</a>
|
||||
release.write=Write
|
||||
release.preview=Preview
|
||||
release.content_placeholder=Write some content
|
||||
release.loading=Loading...
|
||||
release.prerelease_desc=This is a pre-release
|
||||
release.prerelease_helper=We’ll point out that this release is identified as non-production ready.
|
||||
release.publish=Publish Release
|
||||
release.save_draft=Save Draft
|
||||
release.edit_release=Edit Release
|
||||
release.tag_name_already_exist=Release with this tag name has already existed.
|
||||
release.edit=Éditer
|
||||
release.ahead=<strong>%d</strong> commissions à %s depuis cette publication
|
||||
release.source_code=Code Source
|
||||
release.tag_name=Nom du tag
|
||||
release.target=Cible
|
||||
release.tag_helper=Choisissez un tag existant ou créez-en un nouveau à publier.
|
||||
release.release_title=Titre de la publication
|
||||
release.content_with_md=Contenu avec <a href="%s">Démarque(s)</a>
|
||||
release.write=Écrire
|
||||
release.preview=Prévisualiser
|
||||
release.content_placeholder=Rédiger du contenu
|
||||
release.loading=Chargement…
|
||||
release.prerelease_desc=Il s'agit d'une version préliminaire
|
||||
release.prerelease_helper=Nous soulignerons que cette version est considérée comme non prête pour la production.
|
||||
release.publish=Publier
|
||||
release.save_draft=Sauvegarder le Brouillon
|
||||
release.edit_release=Éditer la publication
|
||||
release.tag_name_already_exist=Une publication avec ce nom de tag a déjà existée.
|
||||
|
||||
[org]
|
||||
org_name_holder=Nom d'organisation
|
||||
|
||||
@@ -377,29 +377,29 @@ diff.stats_desc=共有 <strong> %d 個文件被更改</strong>,包括 <strong>
|
||||
diff.bin=二進制
|
||||
diff.view_file=查看文件
|
||||
|
||||
release.releases=Releases
|
||||
release.new_release=New Release
|
||||
release.draft=Draft
|
||||
release.prerelease=Pre-Release
|
||||
release.stable=Stable
|
||||
release.edit=edit
|
||||
release.ahead=<strong>%d</strong> commits to %s since this release
|
||||
release.source_code=Source Code
|
||||
release.tag_name=Tag name
|
||||
release.target=Target
|
||||
release.tag_helper=Choose an existing tag, or create a new tag on publish.
|
||||
release.release_title=Release title
|
||||
release.content_with_md=Content with <a href="%s">Markdown</a>
|
||||
release.write=Write
|
||||
release.preview=Preview
|
||||
release.content_placeholder=Write some content
|
||||
release.loading=Loading...
|
||||
release.prerelease_desc=This is a pre-release
|
||||
release.prerelease_helper=We’ll point out that this release is identified as non-production ready.
|
||||
release.publish=Publish Release
|
||||
release.save_draft=Save Draft
|
||||
release.edit_release=Edit Release
|
||||
release.tag_name_already_exist=Release with this tag name has already existed.
|
||||
release.releases=版本發佈
|
||||
release.new_release=發佈新版本
|
||||
release.draft=草稿
|
||||
release.prerelease=預發佈版本
|
||||
release.stable=穩定
|
||||
release.edit=編輯
|
||||
release.ahead=在該版本發佈之後已有 <strong>%d</strong> 次代碼提交到 %s 分支
|
||||
release.source_code=源代碼
|
||||
release.tag_name=標籤名稱
|
||||
release.target=目標分支
|
||||
release.tag_helper=選擇或創建一個已存在的標籤
|
||||
release.release_title=發佈標題
|
||||
release.content_with_md=使用 <a href="%s">Markdown</a> 編輯內容
|
||||
release.write=內容編輯
|
||||
release.preview=效果預覽
|
||||
release.content_placeholder=請輸入內容
|
||||
release.loading=正在加載...
|
||||
release.prerelease_desc=這是一個預發佈版本
|
||||
release.prerelease_helper=我們會告知用戶不建議將本發佈投入生產環境使用。
|
||||
release.publish=發佈版本
|
||||
release.save_draft=保在草稿
|
||||
release.edit_release=編輯發佈信息
|
||||
release.tag_name_already_exist=已經存在使用相同標籤的發佈版本。
|
||||
|
||||
[org]
|
||||
org_name_holder=組織名稱
|
||||
|
||||
@@ -30,7 +30,7 @@ The `config` file contains lines which will in the gogs docker container end up
|
||||
Here you can define things like the MySQL server for your database block.
|
||||
|
||||
The `fig` file will just be added to `fig.yml`, which is used by fig to manage your containers.
|
||||
This inculdes container linking!
|
||||
This includes container linking!
|
||||
|
||||
Just have a look at them and it will be clear how to write your own blocks.
|
||||
|
||||
@@ -53,7 +53,7 @@ Example:
|
||||
More sophisticated Example
|
||||
--------------------------
|
||||
|
||||
Her is a more elaborated example
|
||||
Here is a more elaborated example
|
||||
|
||||
```sh
|
||||
./assemble_blocks.sh docker_gogs w_db_cache_session option_db_postgresql option_cache_redis option_session_mysql
|
||||
@@ -86,4 +86,4 @@ This will pull in the `Dockerfile` from `docker_gogs` instead of the one from `d
|
||||
`Dockerfile`s for the `master` and `dev` branch are provided as `docker_gogs` and `docker_gogs_dev`
|
||||
|
||||
|
||||
[fig]:http://www.fig.sh/
|
||||
[fig]:http://www.fig.sh/
|
||||
|
||||
@@ -46,7 +46,7 @@ ENV HOME /home/git
|
||||
ENV USER git
|
||||
ENV PATH $GOGS_PATH:$PATH
|
||||
|
||||
RUN git config --global user.name "GoGS"
|
||||
RUN git config --global user.name "GoGS" && git config --global user.email "gogitservice@gmail.com"
|
||||
|
||||
ENTRYPOINT ["/tmp/init_gogs.sh"]
|
||||
CMD ["gogs", "web"]
|
||||
|
||||
@@ -47,7 +47,7 @@ ENV HOME /home/git
|
||||
ENV USER git
|
||||
ENV PATH $GOGS_PATH:$PATH
|
||||
|
||||
RUN git config --global user.name "GoGS"
|
||||
RUN git config --global user.name "GoGS" && git config --global user.email "gogitservice@gmail.com"
|
||||
|
||||
ENTRYPOINT ["/tmp/init_gogs.sh"]
|
||||
CMD ["gogs", "web"]
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.5.9.1213 Beta"
|
||||
const APP_VER = "0.5.11.0103 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -472,8 +472,8 @@ func UpdateIssueUserPairByAssignee(aid, iid int64) error {
|
||||
if aid == 0 {
|
||||
return nil
|
||||
}
|
||||
rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
|
||||
_, err := x.Exec(rawSql, aid, iid)
|
||||
rawSql = "UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?"
|
||||
_, err := x.Exec(rawSql, true, aid, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -45,22 +45,23 @@ func init() {
|
||||
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
||||
new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
|
||||
new(Notice))
|
||||
new(Notice), new(EmailAddress))
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
DbCfg.Type = setting.Cfg.MustValue("database", "DB_TYPE")
|
||||
sec := setting.Cfg.Section("database")
|
||||
DbCfg.Type = sec.Key("DB_TYPE").String()
|
||||
if DbCfg.Type == "sqlite3" {
|
||||
UseSQLite3 = true
|
||||
}
|
||||
DbCfg.Host = setting.Cfg.MustValue("database", "HOST")
|
||||
DbCfg.Name = setting.Cfg.MustValue("database", "NAME")
|
||||
DbCfg.User = setting.Cfg.MustValue("database", "USER")
|
||||
DbCfg.Host = sec.Key("HOST").String()
|
||||
DbCfg.Name = sec.Key("NAME").String()
|
||||
DbCfg.User = sec.Key("USER").String()
|
||||
if len(DbCfg.Pwd) == 0 {
|
||||
DbCfg.Pwd = setting.Cfg.MustValue("database", "PASSWD")
|
||||
DbCfg.Pwd = sec.Key("PASSWD").String()
|
||||
}
|
||||
DbCfg.SslMode = setting.Cfg.MustValue("database", "SSL_MODE")
|
||||
DbCfg.Path = setting.Cfg.MustValue("database", "PATH", "data/gogs.db")
|
||||
DbCfg.SslMode = sec.Key("SSL_MODE").String()
|
||||
DbCfg.Path = sec.Key("PATH").MustString("data/gogs.db")
|
||||
}
|
||||
|
||||
func getEngine() (*xorm.Engine, error) {
|
||||
|
||||
@@ -105,21 +105,18 @@ func NewRepoContext() {
|
||||
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
|
||||
}
|
||||
|
||||
// Check if server has basic git setting and set if not.
|
||||
if stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name"); err != nil || strings.TrimSpace(stdout) == "" {
|
||||
// ExitError indicates user.name is not set
|
||||
if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
|
||||
stndrdUserName := "Gogs"
|
||||
stndrdUserEmail := "gogitservice@gmail.com"
|
||||
if _, stderr, gerr := process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", stndrdUserName); gerr != nil {
|
||||
log.Fatal(4, "Fail to set git user.name(%s): %s", gerr, stderr)
|
||||
// Check if server has user.email and user.name set correctly and set if they're not.
|
||||
for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogitservice@gmail.com"} {
|
||||
if stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", configKey); err != nil || strings.TrimSpace(stdout) == "" {
|
||||
// ExitError indicates this config is not set
|
||||
if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
|
||||
if _, stderr, gerr := process.Exec("NewRepoContext(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil {
|
||||
log.Fatal(4, "Fail to set git %s(%s): %s", configKey, gerr, stderr)
|
||||
}
|
||||
log.Info("Git config %s set to %s", configKey, defaultValue)
|
||||
} else {
|
||||
log.Fatal(4, "Fail to get git %s(%s): %s", configKey, err, stderr)
|
||||
}
|
||||
if _, stderr, gerr := process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", stndrdUserEmail); gerr != nil {
|
||||
log.Fatal(4, "Fail to set git user.email(%s): %s", gerr, stderr)
|
||||
}
|
||||
log.Info("Git user.name and user.email set to %s <%s>", stndrdUserName, stndrdUserEmail)
|
||||
} else {
|
||||
log.Fatal(4, "Fail to get git user.name(%s): %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +829,8 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) error {
|
||||
|
||||
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
|
||||
func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
|
||||
userName = strings.ToLower(userName)
|
||||
oldRepoName = strings.ToLower(oldRepoName)
|
||||
newRepoName = strings.ToLower(newRepoName)
|
||||
if !IsLegalName(newRepoName) {
|
||||
return ErrRepoNameIllegal
|
||||
@@ -839,7 +838,7 @@ func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error)
|
||||
|
||||
// Update accesses.
|
||||
accesses := make([]Access, 0, 10)
|
||||
if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
|
||||
if err = x.Find(&accesses, &Access{RepoName: userName + "/" + oldRepoName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1187,6 +1186,8 @@ func MirrorUpdate() {
|
||||
isMirrorUpdating = true
|
||||
defer func() { isMirrorUpdating = false }()
|
||||
|
||||
mirrors := make([]*Mirror, 0, 10)
|
||||
|
||||
if err := x.Iterate(new(Mirror), func(idx int, bean interface{}) error {
|
||||
m := bean.(*Mirror)
|
||||
if m.NextUpdate.After(time.Now()) {
|
||||
@@ -1197,13 +1198,25 @@ func MirrorUpdate() {
|
||||
if _, stderr, err := process.ExecDir(10*time.Minute,
|
||||
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
|
||||
"git", "remote", "update"); err != nil {
|
||||
return errors.New("git remote update: " + stderr)
|
||||
desc := fmt.Sprintf("Fail to update mirror repository(%s): %s", repoPath, stderr)
|
||||
log.Error(4, desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error(4, "Fail to add notice: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
|
||||
return UpdateMirror(m)
|
||||
mirrors = append(mirrors, m)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error(4, "repo.MirrorUpdate: %v", err)
|
||||
log.Error(4, "MirrorUpdate: %v", err)
|
||||
}
|
||||
|
||||
for i := range mirrors {
|
||||
if err := UpdateMirror(mirrors[i]); err != nil {
|
||||
log.Error(4, "UpdateMirror", fmt.Sprintf("%s: %v", mirrors[i].RepoName, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1215,7 +1228,7 @@ func GitFsck() {
|
||||
isGitFscking = true
|
||||
defer func() { isGitFscking = false }()
|
||||
|
||||
args := append([]string{"fsck"}, setting.GitFsckArgs...)
|
||||
args := append([]string{"fsck"}, setting.Git.Fsck.Args...)
|
||||
if err := x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
@@ -1239,7 +1252,7 @@ func GitFsck() {
|
||||
}
|
||||
|
||||
func GitGcRepos() error {
|
||||
args := append([]string{"gc"}, setting.GitGcArgs...)
|
||||
args := append([]string{"gc"}, setting.Git.GcArgs...)
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
|
||||
184
models/user.go
184
models/user.go
@@ -42,6 +42,8 @@ var (
|
||||
ErrUserNotExist = errors.New("User does not exist")
|
||||
ErrUserNotKeyOwner = errors.New("User does not the owner of public key")
|
||||
ErrEmailAlreadyUsed = errors.New("E-mail already used")
|
||||
ErrEmailNotExist = errors.New("E-mail does not exist")
|
||||
ErrEmailNotActivated = errors.New("E-mail address has not been activated")
|
||||
ErrUserNameIllegal = errors.New("User name contains illegal characters")
|
||||
ErrLoginSourceNotExist = errors.New("Login source does not exist")
|
||||
ErrLoginSourceNotActived = errors.New("Login source is not actived")
|
||||
@@ -50,10 +52,11 @@ var (
|
||||
|
||||
// User represents the object of individual and member of organization.
|
||||
type User struct {
|
||||
Id int64
|
||||
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||
Name string `xorm:"UNIQUE NOT NULL"`
|
||||
FullName string
|
||||
Id int64
|
||||
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||
Name string `xorm:"UNIQUE NOT NULL"`
|
||||
FullName string
|
||||
// Email is the primary email address (to be used for communication).
|
||||
Email string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Passwd string `xorm:"NOT NULL"`
|
||||
LoginType LoginType
|
||||
@@ -93,6 +96,16 @@ type User struct {
|
||||
Members []*User `xorm:"-"`
|
||||
}
|
||||
|
||||
// EmailAdresses is the list of all email addresses of a user. Can contain the
|
||||
// primary email address, but is not obligatory
|
||||
type EmailAddress struct {
|
||||
Id int64
|
||||
Uid int64 `xorm:"INDEX NOT NULL"`
|
||||
Email string `xorm:"UNIQUE NOT NULL"`
|
||||
IsActivated bool
|
||||
IsPrimary bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// DashboardLink returns the user dashboard page link.
|
||||
func (u *User) DashboardLink() string {
|
||||
if u.IsOrganization() {
|
||||
@@ -248,6 +261,9 @@ func IsEmailUsed(email string) (bool, error) {
|
||||
if len(email) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if has, err := x.Get(&EmailAddress{Email: email}); has || err != nil {
|
||||
return has, err
|
||||
}
|
||||
return x.Get(&User{Email: email})
|
||||
}
|
||||
|
||||
@@ -355,6 +371,25 @@ func VerifyUserActiveCode(code string) (user *User) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify active code when active account
|
||||
func VerifyActiveEmailCode(code, email string) *EmailAddress {
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
|
||||
if user := getVerifyUser(code); user != nil {
|
||||
// time limit code
|
||||
prefix := code[:base.TimeLimitCodeLength]
|
||||
data := com.ToStr(user.Id) + email + user.LowerName + user.Passwd + user.Rands
|
||||
|
||||
if base.VerifyTimeLimitCode(data, minutes, prefix) {
|
||||
emailAddress := &EmailAddress{Email: email}
|
||||
if has, _ := x.Get(emailAddress); has {
|
||||
return emailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeUserName changes all corresponding setting from old user name to new one.
|
||||
func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
if !IsLegalName(newUserName) {
|
||||
@@ -418,7 +453,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
|
||||
// UpdateUser updates user's information.
|
||||
func UpdateUser(u *User) error {
|
||||
has, err := x.Where("id != ?", u.Id).And("email = ?", u.Email).Get(new(User))
|
||||
has, err := x.Where("id!=?", u.Id).And("type=?", INDIVIDUAL).And("email=?", u.Email).Get(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
@@ -488,6 +523,10 @@ func DeleteUser(u *User) error {
|
||||
if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete all alternative email addresses
|
||||
if _, err = x.Delete(&EmailAddress{Uid: u.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete all SSH keys.
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = x.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil {
|
||||
@@ -508,9 +547,12 @@ func DeleteUser(u *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteInactivateUsers deletes all inactivate users.
|
||||
// DeleteInactivateUsers deletes all inactivate users and email addresses.
|
||||
func DeleteInactivateUsers() error {
|
||||
_, err := x.Where("is_active=?", false).Delete(new(User))
|
||||
if err == nil {
|
||||
_, err = x.Where("is_activated=?", false).Delete(new(EmailAddress))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -584,6 +626,117 @@ func GetUserIdsByNames(names []string) []int64 {
|
||||
return ids
|
||||
}
|
||||
|
||||
// Get all email addresses
|
||||
func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
|
||||
emails := make([]*EmailAddress, 0, 5)
|
||||
err := x.Where("uid=?", uid).Find(&emails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u, err := GetUserById(uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isPrimaryFound := false
|
||||
|
||||
for _, email := range emails {
|
||||
if email.Email == u.Email {
|
||||
isPrimaryFound = true
|
||||
email.IsPrimary = true
|
||||
} else {
|
||||
email.IsPrimary = false
|
||||
}
|
||||
}
|
||||
|
||||
// We alway want the primary email address displayed, even if it's not in
|
||||
// the emailaddress table (yet)
|
||||
if !isPrimaryFound {
|
||||
emails = append(emails, &EmailAddress{Email: u.Email, IsActivated: true, IsPrimary: true})
|
||||
}
|
||||
return emails, nil
|
||||
}
|
||||
|
||||
func AddEmailAddress(email *EmailAddress) error {
|
||||
used, err := IsEmailUsed(email.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if used {
|
||||
return ErrEmailAlreadyUsed
|
||||
}
|
||||
|
||||
_, err = x.Insert(email)
|
||||
return err
|
||||
}
|
||||
|
||||
func (email *EmailAddress) Activate() error {
|
||||
email.IsActivated = true
|
||||
if _, err := x.Id(email.Id).AllCols().Update(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user, err := GetUserById(email.Uid); err != nil {
|
||||
return err
|
||||
} else {
|
||||
user.Rands = GetUserSalt()
|
||||
return UpdateUser(user)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteEmailAddress(email *EmailAddress) error {
|
||||
has, err := x.Get(email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrEmailNotExist
|
||||
}
|
||||
|
||||
if _, err = x.Delete(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func MakeEmailPrimary(email *EmailAddress) error {
|
||||
has, err := x.Get(email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrEmailNotExist
|
||||
}
|
||||
|
||||
if !email.IsActivated {
|
||||
return ErrEmailNotActivated
|
||||
}
|
||||
|
||||
user := &User{Id: email.Uid}
|
||||
has, err = x.Get(user)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrUserNotExist
|
||||
}
|
||||
|
||||
// Make sure the former primary email doesn't disappear
|
||||
former_primary_email := &EmailAddress{Email: user.Email}
|
||||
has, err = x.Get(former_primary_email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
former_primary_email.Uid = user.Id
|
||||
former_primary_email.IsActivated = user.IsActive
|
||||
x.Insert(former_primary_email)
|
||||
}
|
||||
|
||||
user.Email = email.Email
|
||||
_, err = x.Id(user.Id).AllCols().Update(user)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// UserCommit represents a commit with validation of user.
|
||||
type UserCommit struct {
|
||||
User *User
|
||||
@@ -629,14 +782,27 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
if len(email) == 0 {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
// First try to find the user by primary email
|
||||
user := &User{Email: strings.ToLower(email)}
|
||||
has, err := x.Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return user, nil
|
||||
if has {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Otherwise, check in alternative list for activated email addresses
|
||||
emailAddress := &EmailAddress{Email: strings.ToLower(email), IsActivated: true}
|
||||
has, err = x.Get(emailAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return GetUserById(emailAddress.Uid)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
|
||||
@@ -49,19 +49,19 @@ func validateApiReq(errs binding.Errors, data map[string]interface{}, f auth.For
|
||||
|
||||
if errs[0].FieldNames[0] == field.Name {
|
||||
switch errs[0].Classification {
|
||||
case binding.RequiredError:
|
||||
case binding.ERR_REQUIRED:
|
||||
data["ErrorMsg"] = fieldName + " cannot be empty"
|
||||
case binding.AlphaDashError:
|
||||
case binding.ERR_ALPHA_DASH:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.AlphaDashDotError:
|
||||
case binding.ERR_ALPHA_DASH_DOT:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.MinSizeError:
|
||||
case binding.ERR_MIN_SIZE:
|
||||
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters"
|
||||
case binding.MaxSizeError:
|
||||
case binding.ERR_MAX_SIZE:
|
||||
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters"
|
||||
case binding.EmailError:
|
||||
case binding.ERR_EMAIL:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
|
||||
case binding.UrlError:
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + errs[0].Classification
|
||||
|
||||
@@ -204,19 +204,19 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
|
||||
data["Err_"+field.Name] = true
|
||||
trName := l.Tr("form." + field.Name)
|
||||
switch errs[0].Classification {
|
||||
case binding.RequiredError:
|
||||
case binding.ERR_REQUIRED:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.require_error")
|
||||
case binding.AlphaDashError:
|
||||
case binding.ERR_ALPHA_DASH:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
|
||||
case binding.AlphaDashDotError:
|
||||
case binding.ERR_ALPHA_DASH_DOT:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
|
||||
case binding.MinSizeError:
|
||||
case binding.ERR_MIN_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
|
||||
case binding.MaxSizeError:
|
||||
case binding.ERR_MAX_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
|
||||
case binding.EmailError:
|
||||
case binding.ERR_EMAIL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
||||
case binding.UrlError:
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.url_error")
|
||||
default:
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
|
||||
|
||||
@@ -97,6 +97,14 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type AddEmailForm struct {
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(255)"`
|
||||
Password string `form:"password" binding:"Required;MinSize(6);MaxSize(255)"`
|
||||
|
||||
@@ -16,7 +16,9 @@ var c = New()
|
||||
func NewCronContext() {
|
||||
c.AddFunc("Update mirrors", "@every 1h", models.MirrorUpdate)
|
||||
c.AddFunc("Deliver hooks", fmt.Sprintf("@every %dm", setting.WebhookTaskInterval), models.DeliverHooks)
|
||||
c.AddFunc("Repository health check", "@every 1h", models.GitFsck)
|
||||
if setting.Git.Fsck.Enable {
|
||||
c.AddFunc("Repository health check", fmt.Sprintf("@every %dh", setting.Git.Fsck.Interval), models.GitFsck)
|
||||
}
|
||||
c.Start()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
const (
|
||||
AUTH_ACTIVE base.TplName = "mail/auth/active"
|
||||
AUTH_ACTIVATE_EMAIL base.TplName = "mail/auth/activate_email"
|
||||
AUTH_REGISTER_SUCCESS base.TplName = "mail/auth/register_success"
|
||||
AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd"
|
||||
|
||||
@@ -30,9 +31,7 @@ const (
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func NewMailMessageFrom(To []string, from, subject, body string) Message {
|
||||
msg := NewHtmlMessage(To, from, subject, body)
|
||||
msg.User = setting.MailService.User
|
||||
return msg
|
||||
return NewHtmlMessage(To, from, subject, body)
|
||||
}
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
@@ -64,6 +63,17 @@ func CreateUserActiveCode(u *models.User, startInf interface{}) string {
|
||||
return code
|
||||
}
|
||||
|
||||
// create a time limit code for user active
|
||||
func CreateUserEmailActivateCode(u *models.User, e *models.EmailAddress, startInf interface{}) string {
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
data := com.ToStr(u.Id) + e.Email + u.LowerName + u.Passwd + u.Rands
|
||||
code := base.CreateTimeLimitCode(data, minutes, startInf)
|
||||
|
||||
// add tail hex username
|
||||
code += hex.EncodeToString([]byte(u.LowerName))
|
||||
return code
|
||||
}
|
||||
|
||||
// Send user register mail with active code
|
||||
func SendRegisterMail(r macaron.Render, u *models.User) {
|
||||
code := CreateUserActiveCode(u, nil)
|
||||
@@ -103,6 +113,27 @@ func SendActiveMail(r macaron.Render, u *models.User) {
|
||||
SendAsync(&msg)
|
||||
}
|
||||
|
||||
// Send email to verify secondary email.
|
||||
func SendActivateEmail(r macaron.Render, user *models.User, email *models.EmailAddress) {
|
||||
code := CreateUserEmailActivateCode(user, email, nil)
|
||||
|
||||
subject := "Verify your e-mail address"
|
||||
|
||||
data := GetMailTmplData(user)
|
||||
data["Code"] = code
|
||||
data["Email"] = email.Email
|
||||
body, err := r.HTMLString(string(AUTH_ACTIVATE_EMAIL), data)
|
||||
if err != nil {
|
||||
log.Error(4, "mail.SendActiveMail(fail to render): %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := NewMailMessage([]string{email.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send activate email to %s", user.Id, email.Email)
|
||||
|
||||
SendAsync(&msg)
|
||||
}
|
||||
|
||||
// Send reset password email.
|
||||
func SendResetPasswdMail(r macaron.Render, u *models.User) {
|
||||
code := CreateUserActiveCode(u, nil)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
@@ -20,7 +21,6 @@ type Message struct {
|
||||
From string
|
||||
Subject string
|
||||
Body string
|
||||
User string
|
||||
Type string
|
||||
Massive bool
|
||||
Info string
|
||||
@@ -35,15 +35,14 @@ func (m Message) Content() string {
|
||||
}
|
||||
|
||||
// create mail content
|
||||
content := "From: \"" + m.From + "\" <" + m.User +
|
||||
">\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
|
||||
content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
|
||||
return content
|
||||
}
|
||||
|
||||
var mailQueue chan *Message
|
||||
|
||||
func NewMailerContext() {
|
||||
mailQueue = make(chan *Message, setting.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
|
||||
mailQueue = make(chan *Message, setting.Cfg.Section("mailer").Key("SEND_BUFFER_LEN").MustInt(10))
|
||||
go processMailQueue()
|
||||
}
|
||||
|
||||
@@ -67,29 +66,67 @@ func processMailQueue() {
|
||||
}
|
||||
|
||||
// sendMail allows mail with self-signed certificates.
|
||||
func sendMail(hostAddressWithPort string, auth smtp.Auth, from string, recipients []string, msgContent []byte) error {
|
||||
client, err := smtp.Dial(hostAddressWithPort)
|
||||
func sendMail(settings *setting.Mailer, recipients []string, msgContent []byte) error {
|
||||
host, port, err := net.SplitHostPort(settings.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||
tlsConn := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
tlsconfig := &tls.Config{
|
||||
InsecureSkipVerify: settings.SkipVerify,
|
||||
ServerName: host,
|
||||
}
|
||||
if err = client.StartTLS(tlsConn); err != nil {
|
||||
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
isSecureConn := false
|
||||
// Start TLS directly if the port ends with 465 (SMTPS protocol)
|
||||
if strings.HasSuffix(port, "465") {
|
||||
conn = tls.Client(conn, tlsconfig)
|
||||
isSecureConn = true
|
||||
}
|
||||
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok, _ := client.Extension("AUTH"); ok && auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
// If not using SMTPS, alway use STARTTLS if available
|
||||
hasStartTLS, _ := client.Extension("STARTTLS")
|
||||
if !isSecureConn && hasStartTLS {
|
||||
if err = client.StartTLS(tlsconfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.Mail(from); err != nil {
|
||||
canAuth, options := client.Extension("AUTH")
|
||||
|
||||
if canAuth && len(settings.User) > 0 {
|
||||
var auth smtp.Auth
|
||||
|
||||
if strings.Contains(options, "CRAM-MD5") {
|
||||
auth = smtp.CRAMMD5Auth(settings.User, settings.Passwd)
|
||||
} else if strings.Contains(options, "PLAIN") {
|
||||
auth = smtp.PlainAuth("", settings.User, settings.Passwd, host)
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fromAddress, err := mail.ParseAddress(settings.From); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err = client.Mail(fromAddress.Address); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, rec := range recipients {
|
||||
@@ -116,7 +153,6 @@ func sendMail(hostAddressWithPort string, auth smtp.Auth, from string, recipient
|
||||
// Direct Send mail message
|
||||
func Send(msg *Message) (int, error) {
|
||||
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
|
||||
host := strings.Split(setting.MailService.Host, ":")
|
||||
|
||||
// get message body
|
||||
content := msg.Content()
|
||||
@@ -127,17 +163,12 @@ func Send(msg *Message) (int, error) {
|
||||
return 0, fmt.Errorf("empty email body")
|
||||
}
|
||||
|
||||
var auth smtp.Auth
|
||||
if len(setting.MailService.Passwd) > 0 {
|
||||
auth = smtp.PlainAuth("", setting.MailService.User, setting.MailService.Passwd, host[0])
|
||||
}
|
||||
|
||||
if msg.Massive {
|
||||
// send mail to multiple emails one by one
|
||||
num := 0
|
||||
for _, to := range msg.To {
|
||||
body := []byte("To: " + to + "\r\n" + content)
|
||||
err := sendMail(setting.MailService.Host, auth, msg.From, []string{to}, body)
|
||||
err := sendMail(setting.MailService, []string{to}, body)
|
||||
if err != nil {
|
||||
return num, err
|
||||
}
|
||||
@@ -148,7 +179,7 @@ func Send(msg *Message) (int, error) {
|
||||
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
|
||||
|
||||
// send to multiple emails in one message
|
||||
err := sendMail(setting.MailService.Host, auth, msg.From, msg.To, body)
|
||||
err := sendMail(setting.MailService, msg.To, body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
"github.com/macaron-contrib/oauth2"
|
||||
"github.com/macaron-contrib/session"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
// "github.com/gogits/gogs/modules/ssh"
|
||||
@@ -104,19 +104,24 @@ var (
|
||||
EnableMemcache bool
|
||||
|
||||
// Session settings.
|
||||
SessionProvider string
|
||||
SessionConfig *session.Config
|
||||
SessionConfig session.Options
|
||||
|
||||
// Git settings.
|
||||
MaxGitDiffLines int
|
||||
GitFsckArgs []string
|
||||
GitGcArgs []string
|
||||
Git struct {
|
||||
MaxGitDiffLines int
|
||||
GcArgs []string `delim:" "`
|
||||
Fsck struct {
|
||||
Enable bool
|
||||
Interval int
|
||||
Args []string `delim:" "`
|
||||
} `ini:"git.fsck"`
|
||||
}
|
||||
|
||||
// I18n settings.
|
||||
Langs, Names []string
|
||||
|
||||
// Global setting objects.
|
||||
Cfg *goconfig.ConfigFile
|
||||
Cfg *ini.File
|
||||
ConfRootPath string
|
||||
CustomPath string // Custom directory path.
|
||||
ProdMode bool
|
||||
@@ -157,7 +162,7 @@ func NewConfigContext() {
|
||||
}
|
||||
ConfRootPath = path.Join(workDir, "conf")
|
||||
|
||||
Cfg, err = goconfig.LoadConfigFile(path.Join(workDir, "conf/app.ini"))
|
||||
Cfg, err = ini.Load(path.Join(workDir, "conf/app.ini"))
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to parse 'conf/app.ini': %v", err)
|
||||
}
|
||||
@@ -169,15 +174,19 @@ func NewConfigContext() {
|
||||
|
||||
cfgPath := path.Join(CustomPath, "conf/app.ini")
|
||||
if com.IsFile(cfgPath) {
|
||||
if err = Cfg.AppendFiles(cfgPath); err != nil {
|
||||
if err = Cfg.Append(cfgPath); err != nil {
|
||||
log.Fatal(4, "Fail to load custom 'conf/app.ini': %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Warn("No custom 'conf/app.ini' found, please go to '/install'")
|
||||
}
|
||||
Cfg.NameMapper = ini.AllCapsUnderscore
|
||||
|
||||
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
|
||||
AppUrl = Cfg.MustValue("server", "ROOT_URL", "http://localhost:3000/")
|
||||
LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log"))
|
||||
|
||||
sec := Cfg.Section("server")
|
||||
AppName = Cfg.Section("").Key("APP_NAME").MustString("Gogs: Go Git Service")
|
||||
AppUrl = sec.Key("ROOT_URL").MustString("http://localhost:3000/")
|
||||
if AppUrl[len(AppUrl)-1] != '/' {
|
||||
AppUrl += "/"
|
||||
}
|
||||
@@ -190,43 +199,43 @@ func NewConfigContext() {
|
||||
AppSubUrl = strings.TrimSuffix(url.Path, "/")
|
||||
|
||||
Protocol = HTTP
|
||||
if Cfg.MustValue("server", "PROTOCOL") == "https" {
|
||||
if sec.Key("PROTOCOL").String() == "https" {
|
||||
Protocol = HTTPS
|
||||
CertFile = Cfg.MustValue("server", "CERT_FILE")
|
||||
KeyFile = Cfg.MustValue("server", "KEY_FILE")
|
||||
}
|
||||
if Cfg.MustValue("server", "PROTOCOL") == "fcgi" {
|
||||
CertFile = sec.Key("CERT_FILE").String()
|
||||
KeyFile = sec.Key("KEY_FILE").String()
|
||||
} else if sec.Key("PROTOCOL").String() == "fcgi" {
|
||||
Protocol = FCGI
|
||||
}
|
||||
Domain = Cfg.MustValue("server", "DOMAIN", "localhost")
|
||||
HttpAddr = Cfg.MustValue("server", "HTTP_ADDR", "0.0.0.0")
|
||||
HttpPort = Cfg.MustValue("server", "HTTP_PORT", "3000")
|
||||
SshPort = Cfg.MustInt("server", "SSH_PORT", 22)
|
||||
OfflineMode = Cfg.MustBool("server", "OFFLINE_MODE")
|
||||
DisableRouterLog = Cfg.MustBool("server", "DISABLE_ROUTER_LOG")
|
||||
StaticRootPath = Cfg.MustValue("server", "STATIC_ROOT_PATH", workDir)
|
||||
LogRootPath = Cfg.MustValue("log", "ROOT_PATH", path.Join(workDir, "log"))
|
||||
EnableGzip = Cfg.MustBool("server", "ENABLE_GZIP")
|
||||
Domain = sec.Key("DOMAIN").MustString("localhost")
|
||||
HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
|
||||
HttpPort = sec.Key("HTTP_PORT").MustString("3000")
|
||||
SshPort = sec.Key("SSH_PORT").MustInt(22)
|
||||
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
|
||||
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
|
||||
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
|
||||
EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
|
||||
|
||||
switch Cfg.MustValue("server", "LANDING_PAGE", "home") {
|
||||
switch sec.Key("LANDING_PAGE").MustString("home") {
|
||||
case "explore":
|
||||
LandingPageUrl = LANDING_PAGE_EXPLORE
|
||||
default:
|
||||
LandingPageUrl = LANDING_PAGE_HOME
|
||||
}
|
||||
|
||||
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK")
|
||||
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
|
||||
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
|
||||
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
|
||||
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
|
||||
ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
|
||||
sec = Cfg.Section("security")
|
||||
InstallLock = sec.Key("INSTALL_LOCK").MustBool()
|
||||
SecretKey = sec.Key("SECRET_KEY").String()
|
||||
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
|
||||
CookieUserName = sec.Key("COOKIE_USERNAME").String()
|
||||
CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
|
||||
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
|
||||
|
||||
AttachmentPath = Cfg.MustValue("attachment", "PATH", "data/attachments")
|
||||
AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "image/jpeg|image/png")
|
||||
AttachmentMaxSize = Cfg.MustInt64("attachment", "MAX_SIZE", 32)
|
||||
AttachmentMaxFiles = Cfg.MustInt("attachment", "MAX_FILES", 10)
|
||||
AttachmentEnabled = Cfg.MustBool("attachment", "ENABLE", true)
|
||||
sec = Cfg.Section("attachment")
|
||||
AttachmentPath = sec.Key("PATH").MustString("data/attachments")
|
||||
AttachmentAllowedTypes = sec.Key("ALLOWED_TYPES").MustString("image/jpeg|image/png")
|
||||
AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(32)
|
||||
AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(10)
|
||||
AttachmentEnabled = sec.Key("ENABLE").MustBool(true)
|
||||
|
||||
TimeFormat = map[string]string{
|
||||
"ANSIC": time.ANSIC,
|
||||
@@ -244,13 +253,13 @@ func NewConfigContext() {
|
||||
"StampMilli": time.StampMilli,
|
||||
"StampMicro": time.StampMicro,
|
||||
"StampNano": time.StampNano,
|
||||
}[Cfg.MustValue("time", "FORMAT", "RFC1123")]
|
||||
}[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
|
||||
|
||||
if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
|
||||
log.Fatal(4, "Could not create directory %s: %s", AttachmentPath, err)
|
||||
}
|
||||
|
||||
RunUser = Cfg.MustValue("", "RUN_USER")
|
||||
RunUser = Cfg.Section("").Key("RUN_USER").String()
|
||||
curUser := os.Getenv("USER")
|
||||
if len(curUser) == 0 {
|
||||
curUser = os.Getenv("USERNAME")
|
||||
@@ -265,36 +274,36 @@ func NewConfigContext() {
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to get home directory: %v", err)
|
||||
}
|
||||
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
|
||||
sec = Cfg.Section("repository")
|
||||
RepoRootPath = sec.Key("ROOT").MustString(filepath.Join(homeDir, "gogs-repositories"))
|
||||
if !filepath.IsAbs(RepoRootPath) {
|
||||
RepoRootPath = filepath.Join(workDir, RepoRootPath)
|
||||
} else {
|
||||
RepoRootPath = filepath.Clean(RepoRootPath)
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
|
||||
log.Fatal(4, "Fail to create repository root path(%s): %v", RepoRootPath, err)
|
||||
}
|
||||
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
|
||||
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
|
||||
|
||||
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server", []string{"server"})
|
||||
AvatarUploadPath = Cfg.MustValue("picture", "AVATAR_UPLOAD_PATH", "data/avatars")
|
||||
sec = Cfg.Section("picture")
|
||||
PictureService = sec.Key("SERVICE").In("server", []string{"server"})
|
||||
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars")
|
||||
os.MkdirAll(AvatarUploadPath, os.ModePerm)
|
||||
|
||||
switch Cfg.MustValue("picture", "GRAVATAR_SOURCE", "gravatar") {
|
||||
switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") {
|
||||
case "duoshuo":
|
||||
GravatarSource = "http://gravatar.duoshuo.com/avatar/"
|
||||
default:
|
||||
GravatarSource = "//1.gravatar.com/avatar/"
|
||||
}
|
||||
DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR")
|
||||
DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
|
||||
|
||||
MaxGitDiffLines = Cfg.MustInt("git", "MAX_GITDIFF_LINES", 10000)
|
||||
GitFsckArgs = Cfg.MustValueArray("git", "FSCK_ARGS", " ")
|
||||
GitGcArgs = Cfg.MustValueArray("git", "GC_ARGS", " ")
|
||||
if err = Cfg.Section("git").MapTo(&Git); err != nil {
|
||||
log.Fatal(4, "Fail to map Git settings: %v", err)
|
||||
}
|
||||
|
||||
Langs = Cfg.MustValueArray("i18n", "LANGS", ",")
|
||||
Names = Cfg.MustValueArray("i18n", "NAMES", ",")
|
||||
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
|
||||
Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
|
||||
|
||||
HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
|
||||
}
|
||||
@@ -312,13 +321,13 @@ var Service struct {
|
||||
}
|
||||
|
||||
func newService() {
|
||||
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
|
||||
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
|
||||
Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
|
||||
Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
|
||||
Service.EnableReverseProxyAutoRegister = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTO_REGISTERATION")
|
||||
Service.ActiveCodeLives = Cfg.Section("service").Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
|
||||
Service.ResetPwdCodeLives = Cfg.Section("service").Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
|
||||
Service.DisableRegistration = Cfg.Section("service").Key("DISABLE_REGISTRATION").MustBool()
|
||||
Service.RequireSignInView = Cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
||||
Service.EnableCacheAvatar = Cfg.Section("service").Key("ENABLE_CACHE_AVATAR").MustBool()
|
||||
Service.EnableReverseProxyAuth = Cfg.Section("service").Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
|
||||
Service.EnableReverseProxyAutoRegister = Cfg.Section("service").Key("ENABLE_REVERSE_PROXY_AUTO_REGISTERATION").MustBool()
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
@@ -334,17 +343,17 @@ func newLogService() {
|
||||
log.Info("%s %s", AppName, AppVer)
|
||||
|
||||
// Get and check log mode.
|
||||
LogModes = strings.Split(Cfg.MustValue("log", "MODE", "console"), ",")
|
||||
LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",")
|
||||
LogConfigs = make([]string, len(LogModes))
|
||||
for i, mode := range LogModes {
|
||||
mode = strings.TrimSpace(mode)
|
||||
modeSec := "log." + mode
|
||||
if _, err := Cfg.GetSection(modeSec); err != nil {
|
||||
sec, err := Cfg.GetSection("log." + mode)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Unknown log mode: %s", mode)
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.MustValueRange("log."+mode, "LEVEL", "Trace",
|
||||
levelName := Cfg.Section("log."+mode).Key("LEVEL").In("Trace",
|
||||
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
@@ -356,42 +365,42 @@ func newLogService() {
|
||||
case "console":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level)
|
||||
case "file":
|
||||
logPath := Cfg.MustValue(modeSec, "FILE_NAME", path.Join(LogRootPath, "gogs.log"))
|
||||
logPath := sec.Key("FILE_NAME").MustString(path.Join(LogRootPath, "gogs.log"))
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
LogConfigs[i] = fmt.Sprintf(
|
||||
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
|
||||
logPath,
|
||||
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
|
||||
1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
|
||||
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
|
||||
sec.Key("LOG_ROTATE").MustBool(true),
|
||||
sec.Key("MAX_LINES").MustInt(1000000),
|
||||
1<<uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)),
|
||||
sec.Key("DAILY_ROTATE").MustBool(true),
|
||||
sec.Key("MAX_DAYS").MustInt(7))
|
||||
case "conn":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
|
||||
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG"),
|
||||
Cfg.MustBool(modeSec, "RECONNECT"),
|
||||
Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
|
||||
Cfg.MustValue(modeSec, "ADDR", ":7020"))
|
||||
sec.Key("RECONNECT_ON_MSG").MustBool(),
|
||||
sec.Key("RECONNECT").MustBool(),
|
||||
sec.Key("PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"}),
|
||||
sec.Key("ADDR").MustString(":7020"))
|
||||
case "smtp":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "USER", "example@example.com"),
|
||||
Cfg.MustValue(modeSec, "PASSWD", "******"),
|
||||
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
|
||||
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
|
||||
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
|
||||
sec.Key("USER").MustString("example@example.com"),
|
||||
sec.Key("PASSWD").MustString("******"),
|
||||
sec.Key("HOST").MustString("127.0.0.1:25"),
|
||||
sec.Key("RECEIVERS").MustString("[]"),
|
||||
sec.Key("SUBJECT").MustString("Diagnostic message from serve"))
|
||||
case "database":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"driver":"%s","conn":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "DRIVER"),
|
||||
Cfg.MustValue(modeSec, "CONN"))
|
||||
sec.Key("DRIVER").String(),
|
||||
sec.Key("CONN").String())
|
||||
}
|
||||
|
||||
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, LogConfigs[i])
|
||||
log.NewLogger(Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000), mode, LogConfigs[i])
|
||||
log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
|
||||
}
|
||||
}
|
||||
|
||||
func newCacheService() {
|
||||
CacheAdapter = Cfg.MustValueRange("cache", "ADAPTER", "memory", []string{"memory", "redis", "memcache"})
|
||||
CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
|
||||
if EnableRedis {
|
||||
log.Info("Redis Enabled")
|
||||
}
|
||||
@@ -401,9 +410,9 @@ func newCacheService() {
|
||||
|
||||
switch CacheAdapter {
|
||||
case "memory":
|
||||
CacheInternal = Cfg.MustInt("cache", "INTERVAL", 60)
|
||||
CacheInternal = Cfg.Section("cache").Key("INTERVAL").MustInt(60)
|
||||
case "redis", "memcache":
|
||||
CacheConn = strings.Trim(Cfg.MustValue("cache", "HOST"), "\" ")
|
||||
CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ")
|
||||
default:
|
||||
log.Fatal(4, "Unknown cache adapter: %s", CacheAdapter)
|
||||
}
|
||||
@@ -412,21 +421,14 @@ func newCacheService() {
|
||||
}
|
||||
|
||||
func newSessionService() {
|
||||
SessionProvider = Cfg.MustValueRange("session", "PROVIDER", "memory",
|
||||
SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
|
||||
[]string{"memory", "file", "redis", "mysql"})
|
||||
|
||||
SessionConfig = new(session.Config)
|
||||
SessionConfig.ProviderConfig = strings.Trim(Cfg.MustValue("session", "PROVIDER_CONFIG"), "\" ")
|
||||
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
|
||||
SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").String(), "\" ")
|
||||
SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gogits")
|
||||
SessionConfig.CookiePath = AppSubUrl
|
||||
SessionConfig.Secure = Cfg.MustBool("session", "COOKIE_SECURE")
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.Gclifetime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.Maxlifetime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool()
|
||||
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
|
||||
SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
|
||||
|
||||
log.Info("Session Service Enabled")
|
||||
}
|
||||
@@ -437,6 +439,7 @@ type Mailer struct {
|
||||
Host string
|
||||
From string
|
||||
User, Passwd string
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
type OauthInfo struct {
|
||||
@@ -457,23 +460,25 @@ var (
|
||||
)
|
||||
|
||||
func newMailService() {
|
||||
sec := Cfg.Section("mailer")
|
||||
// Check mailer setting.
|
||||
if !Cfg.MustBool("mailer", "ENABLED") {
|
||||
if !sec.Key("ENABLED").MustBool() {
|
||||
return
|
||||
}
|
||||
|
||||
MailService = &Mailer{
|
||||
Name: Cfg.MustValue("mailer", "NAME", AppName),
|
||||
Host: Cfg.MustValue("mailer", "HOST"),
|
||||
User: Cfg.MustValue("mailer", "USER"),
|
||||
Passwd: Cfg.MustValue("mailer", "PASSWD"),
|
||||
Name: sec.Key("NAME").MustString(AppName),
|
||||
Host: sec.Key("HOST").String(),
|
||||
User: sec.Key("USER").String(),
|
||||
Passwd: sec.Key("PASSWD").String(),
|
||||
SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
|
||||
}
|
||||
MailService.From = Cfg.MustValue("mailer", "FROM", MailService.User)
|
||||
MailService.From = sec.Key("FROM").MustString(MailService.User)
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newRegisterMailService() {
|
||||
if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") {
|
||||
if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Register Mail Service: Mail Service is not enabled")
|
||||
@@ -484,7 +489,7 @@ func newRegisterMailService() {
|
||||
}
|
||||
|
||||
func newNotifyMailService() {
|
||||
if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") {
|
||||
if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
||||
@@ -495,8 +500,8 @@ func newNotifyMailService() {
|
||||
}
|
||||
|
||||
func newWebhookService() {
|
||||
WebhookTaskInterval = Cfg.MustInt("webhook", "TASK_INTERVAL", 1)
|
||||
WebhookDeliverTimeout = Cfg.MustInt("webhook", "DELIVER_TIMEOUT", 5)
|
||||
WebhookTaskInterval = Cfg.Section("webhook").Key("TASK_INTERVAL").MustInt(1)
|
||||
WebhookDeliverTimeout = Cfg.Section("webhook").Key("DELIVER_TIMEOUT").MustInt(5)
|
||||
}
|
||||
|
||||
func NewServices() {
|
||||
|
||||
@@ -35,7 +35,7 @@ var (
|
||||
)
|
||||
|
||||
func NewOauthService() {
|
||||
if !setting.Cfg.MustBool("oauth", "ENABLED") {
|
||||
if !setting.Cfg.Section("oauth").Key("ENABLED").MustBool() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,20 +48,21 @@ func NewOauthService() {
|
||||
allOauthes := []string{"github", "google", "qq", "twitter", "weibo"}
|
||||
// Load all OAuth config data.
|
||||
for _, name := range allOauthes {
|
||||
if !setting.Cfg.MustBool("oauth."+name, "ENABLED") {
|
||||
sec := setting.Cfg.Section("oauth." + name)
|
||||
if !sec.Key("ENABLED").MustBool() {
|
||||
continue
|
||||
}
|
||||
setting.OauthService.OauthInfos[name] = &setting.OauthInfo{
|
||||
Options: oauth2.Options{
|
||||
ClientID: setting.Cfg.MustValue("oauth."+name, "CLIENT_ID"),
|
||||
ClientSecret: setting.Cfg.MustValue("oauth."+name, "CLIENT_SECRET"),
|
||||
Scopes: setting.Cfg.MustValueArray("oauth."+name, "SCOPES", " "),
|
||||
ClientID: sec.Key("CLIENT_ID").String(),
|
||||
ClientSecret: sec.Key("CLIENT_SECRET").String(),
|
||||
Scopes: sec.Key("SCOPES").Strings(" "),
|
||||
PathLogin: "/user/login/oauth2/" + name,
|
||||
PathCallback: setting.AppSubUrl + "/user/login/" + name,
|
||||
RedirectURL: setting.AppUrl + "user/login/" + name,
|
||||
},
|
||||
AuthUrl: setting.Cfg.MustValue("oauth."+name, "AUTH_URL"),
|
||||
TokenUrl: setting.Cfg.MustValue("oauth."+name, "TOKEN_URL"),
|
||||
AuthUrl: sec.Key("AUTH_URL").String(),
|
||||
TokenUrl: sec.Key("TOKEN_URL").String(),
|
||||
}
|
||||
socialConfigs[name] = &oauth2.Options{
|
||||
ClientID: setting.OauthService.OauthInfos[name].ClientID,
|
||||
@@ -72,35 +73,35 @@ func NewOauthService() {
|
||||
enabledOauths := make([]string, 0, 10)
|
||||
|
||||
// GitHub.
|
||||
if setting.Cfg.MustBool("oauth.github", "ENABLED") {
|
||||
if setting.Cfg.Section("oauth.github").Key("ENABLED").MustBool() {
|
||||
setting.OauthService.GitHub = true
|
||||
newGitHubOauth(socialConfigs["github"])
|
||||
enabledOauths = append(enabledOauths, "GitHub")
|
||||
}
|
||||
|
||||
// Google.
|
||||
if setting.Cfg.MustBool("oauth.google", "ENABLED") {
|
||||
if setting.Cfg.Section("oauth.google").Key("ENABLED").MustBool() {
|
||||
setting.OauthService.Google = true
|
||||
newGoogleOauth(socialConfigs["google"])
|
||||
enabledOauths = append(enabledOauths, "Google")
|
||||
}
|
||||
|
||||
// QQ.
|
||||
if setting.Cfg.MustBool("oauth.qq", "ENABLED") {
|
||||
if setting.Cfg.Section("oauth.qq").Key("ENABLED").MustBool() {
|
||||
setting.OauthService.Tencent = true
|
||||
newTencentOauth(socialConfigs["qq"])
|
||||
enabledOauths = append(enabledOauths, "QQ")
|
||||
}
|
||||
|
||||
// Twitter.
|
||||
// if setting.Cfg.MustBool("oauth.twitter", "ENABLED") {
|
||||
// if setting.Cfg.Section("oauth.twitter").Key( "ENABLED").MustBool() {
|
||||
// setting.OauthService.Twitter = true
|
||||
// newTwitterOauth(socialConfigs["twitter"])
|
||||
// enabledOauths = append(enabledOauths, "Twitter")
|
||||
// }
|
||||
|
||||
// Weibo.
|
||||
if setting.Cfg.MustBool("oauth.weibo", "ENABLED") {
|
||||
if setting.Cfg.Section("oauth.weibo").Key("ENABLED").MustBool() {
|
||||
setting.OauthService.Weibo = true
|
||||
newWeiboOauth(socialConfigs["weibo"])
|
||||
enabledOauths = append(enabledOauths, "Weibo")
|
||||
|
||||
@@ -1250,6 +1250,9 @@ The register and sign-in page style
|
||||
color: #428BCA;
|
||||
text-decoration: underline;
|
||||
}
|
||||
#repo-files-table td.message .text-truncate {
|
||||
max-width: 360px;
|
||||
}
|
||||
#repo-files-table tbody {
|
||||
background-color: #FFF;
|
||||
}
|
||||
@@ -2250,15 +2253,15 @@ textarea#issue-add-content {
|
||||
#labels-num {
|
||||
margin-right: 1em;
|
||||
}
|
||||
#label-list .right {
|
||||
#label-list a.right {
|
||||
margin-left: 1em;
|
||||
color: #999;
|
||||
line-height: 30px;
|
||||
}
|
||||
#label-list .right i {
|
||||
#label-list a.right i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
#label-list .right:hover {
|
||||
#label-list a.right:hover {
|
||||
color: #444444;
|
||||
}
|
||||
#label-list .num {
|
||||
@@ -2279,37 +2282,46 @@ textarea#issue-add-content {
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed #AAA;
|
||||
}
|
||||
#label-add-form {
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
#label-add-form .ipt {
|
||||
#label-add-form .ipt,
|
||||
#label-edit-form .ipt,
|
||||
#label-delete-form .ipt {
|
||||
font-size: 14px;
|
||||
}
|
||||
#label-add-form .ipt[name=name] {
|
||||
#label-add-form .ipt[name=name],
|
||||
#label-edit-form .ipt[name=name],
|
||||
#label-delete-form .ipt[name=name] {
|
||||
width: 300px;
|
||||
}
|
||||
#label-add-form .btn {
|
||||
#label-add-form .btn,
|
||||
#label-edit-form .btn,
|
||||
#label-delete-form .btn {
|
||||
height: 33px;
|
||||
font-size: 14px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
#label-color-drop .ipt {
|
||||
#label-add-form {
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
#label-delete-form span {
|
||||
line-height: 33px;
|
||||
}
|
||||
.label-color-drop .ipt {
|
||||
width: 100px;
|
||||
}
|
||||
#label-color-drop .drop-down {
|
||||
width: 128px;
|
||||
top: 22px;
|
||||
left: 50px;
|
||||
.label-color-drop .drop-down {
|
||||
width: 128px !important;
|
||||
top: 22px !important;
|
||||
left: 50px !important;
|
||||
padding: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
#label-color-drop .drop-down a.color {
|
||||
.label-color-drop .drop-down a.color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
#label-color-drop label {
|
||||
.label-color-drop label {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
|
||||
@@ -214,7 +214,7 @@ var Gogs = {};
|
||||
if (json.ok && json.data.length) {
|
||||
var html = '';
|
||||
$.each(json.data, function (i, item) {
|
||||
html += '<li><a><img src="' + item.avatar_url + '">' + item.username;
|
||||
html += '<li><a><img src="' + item.avatar_url + '"><span class="username">' + item.username + '</span>';
|
||||
if (notEmpty(item.full_name)) {
|
||||
html += ' (' + item.full_name + ')';
|
||||
}
|
||||
@@ -571,7 +571,7 @@ function initInvite() {
|
||||
$ul.toggleShow();
|
||||
}
|
||||
}).next().next().find('ul').on("click", 'li', function () {
|
||||
$('#org-member-invite').val($(this).text());
|
||||
$('#org-member-invite').val($(this).find('.username').text());
|
||||
$ul.toggleHide();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,22 +14,29 @@ $(document).ready(function(){
|
||||
|
||||
// render label color input
|
||||
var color_input = $('#label-add-color');
|
||||
var color_label = $('#label-color-drop label');
|
||||
var color_label = $('#label-add-form .label-color-drop label');
|
||||
color_label.css("background-color",labelColors[0]);
|
||||
color_input.val(labelColors[0]);
|
||||
|
||||
|
||||
// render label color drop
|
||||
$('#label-color-drop .drop-down')
|
||||
.html(colorDropHtml)
|
||||
.on("click","a",function(){
|
||||
var color = $(this).data("color-hex");
|
||||
color_label.css("background-color",color);
|
||||
color_input.val(color);
|
||||
});
|
||||
function render_color_drop($e){
|
||||
$e.find('.label-color-drop .drop-down')
|
||||
.html(colorDropHtml)
|
||||
.on("click","a",function(){
|
||||
var $form = $(this).parents(".form");
|
||||
var color_label = $form.find(".label-color-drop label");
|
||||
var color_input = $form.find("input[name=color]");
|
||||
var color = $(this).data("color-hex");
|
||||
color_label.css("background-color",color);
|
||||
color_input.val(color);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// color drop visible
|
||||
var form = $('#label-add-form');
|
||||
render_color_drop(form);
|
||||
$('#label-new-btn').on("click",function(){
|
||||
if(form.hasClass("hidden")){
|
||||
form.removeClass("hidden");
|
||||
@@ -37,7 +44,54 @@ $(document).ready(function(){
|
||||
});
|
||||
$('#label-cancel-btn').on("click",function(){
|
||||
form.addClass("hidden");
|
||||
})
|
||||
});
|
||||
|
||||
// label edit form render
|
||||
var $edit_form_tpl = $("#label-edit-form-tpl");
|
||||
$("#label-list").on("click","a.edit",function(){
|
||||
var $label_item = $(this).parents(".item");
|
||||
var $clone_form = $edit_form_tpl.clone();
|
||||
render_color_drop($clone_form);
|
||||
|
||||
// add default color
|
||||
var color_label = $clone_form.find(".label-color-drop label");
|
||||
var color_input = $clone_form.find("input[name=color]");
|
||||
var color = $label_item.find(".label").data("color-hex");
|
||||
color_label.css("background-color",color);
|
||||
color_input.val(color);
|
||||
|
||||
// add label name
|
||||
$clone_form.find("input[name=name]").val($label_item.find(".label").text());
|
||||
|
||||
// add label id
|
||||
$clone_form.find("input[name=id]").val($label_item.attr("id").replace("label-",""));
|
||||
|
||||
// append form
|
||||
$label_item.after($clone_form.show());
|
||||
|
||||
// add cancel button event
|
||||
$('#label-edit-cancel-btn').on("click",function(){
|
||||
$clone_form.remove();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// label delete form render
|
||||
var $del_form_tpl = $('#label-delete-form-tpl');
|
||||
$("#label-list").on("click","a.delete",function(){
|
||||
var $label_item = $(this).parents(".item");
|
||||
var $clone_form = $del_form_tpl.clone();
|
||||
|
||||
// add label id
|
||||
$clone_form.find("input[name=id]").val($label_item.attr("id").replace("label-",""));
|
||||
|
||||
// append form
|
||||
$label_item.after($clone_form.show());
|
||||
|
||||
// add cancel button event
|
||||
$('#label-del-cancel-btn').on("click",function(){
|
||||
$clone_form.remove();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
8
public/ng/js/min/gogs-min.js
vendored
8
public/ng/js/min/gogs-min.js
vendored
File diff suppressed because one or more lines are too long
@@ -384,7 +384,7 @@ textarea#issue-add-content {
|
||||
}
|
||||
// labels list
|
||||
#label-list {
|
||||
.right {
|
||||
a.right {
|
||||
margin-left: 1em;
|
||||
color: #999;
|
||||
i {
|
||||
@@ -416,10 +416,10 @@ textarea#issue-add-content {
|
||||
border-bottom: 1px dashed #AAA;
|
||||
}
|
||||
}
|
||||
// label add form
|
||||
#label-add-form{
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
// label add form, label edit form
|
||||
#label-add-form,
|
||||
#label-edit-form,
|
||||
#label-delete-form{
|
||||
.ipt{
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -432,15 +432,24 @@ textarea#issue-add-content {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
#label-add-form{
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
#label-delete-form{
|
||||
span{
|
||||
line-height: 33px;
|
||||
}
|
||||
}
|
||||
// label color drop
|
||||
#label-color-drop{
|
||||
.label-color-drop{
|
||||
.ipt{
|
||||
width:100px;
|
||||
}
|
||||
.drop-down{
|
||||
width:128px;
|
||||
top:22px;
|
||||
left:50px;
|
||||
width:128px !important;
|
||||
top:22px !important;
|
||||
left:50px !important;
|
||||
padding: 12px;
|
||||
line-height: 16px;
|
||||
a.color{
|
||||
|
||||
@@ -281,6 +281,11 @@
|
||||
color: #428BCA;
|
||||
text-decoration: underline;
|
||||
}
|
||||
td.message {
|
||||
.text-truncate {
|
||||
max-width: 360px;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
background-color: #FFF;
|
||||
tr:hover {
|
||||
|
||||
@@ -202,7 +202,6 @@ func Config(ctx *middleware.Context) {
|
||||
ctx.Data["CacheInternal"] = setting.CacheInternal
|
||||
ctx.Data["CacheConn"] = setting.CacheConn
|
||||
|
||||
ctx.Data["SessionProvider"] = setting.SessionProvider
|
||||
ctx.Data["SessionConfig"] = setting.SessionConfig
|
||||
|
||||
ctx.Data["PictureService"] = setting.PictureService
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
@@ -32,7 +31,7 @@ const (
|
||||
)
|
||||
|
||||
func checkRunMode() {
|
||||
switch setting.Cfg.MustValue("", "RUN_MODE") {
|
||||
switch setting.Cfg.Section("").Key("RUN_MODE").String() {
|
||||
case "prod":
|
||||
macaron.Env = macaron.PROD
|
||||
setting.ProdMode = true
|
||||
@@ -210,38 +209,40 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
|
||||
}
|
||||
|
||||
// Save settings.
|
||||
setting.Cfg.SetValue("database", "DB_TYPE", models.DbCfg.Type)
|
||||
setting.Cfg.SetValue("database", "HOST", models.DbCfg.Host)
|
||||
setting.Cfg.SetValue("database", "NAME", models.DbCfg.Name)
|
||||
setting.Cfg.SetValue("database", "USER", models.DbCfg.User)
|
||||
setting.Cfg.SetValue("database", "PASSWD", models.DbCfg.Pwd)
|
||||
setting.Cfg.SetValue("database", "SSL_MODE", models.DbCfg.SslMode)
|
||||
setting.Cfg.SetValue("database", "PATH", models.DbCfg.Path)
|
||||
setting.Cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type)
|
||||
setting.Cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host)
|
||||
setting.Cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name)
|
||||
setting.Cfg.Section("database").Key("USER").SetValue(models.DbCfg.User)
|
||||
setting.Cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Pwd)
|
||||
setting.Cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SslMode)
|
||||
setting.Cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path)
|
||||
|
||||
setting.Cfg.SetValue("repository", "ROOT", form.RepoRootPath)
|
||||
setting.Cfg.SetValue("", "RUN_USER", form.RunUser)
|
||||
setting.Cfg.SetValue("server", "DOMAIN", form.Domain)
|
||||
setting.Cfg.SetValue("server", "ROOT_URL", form.AppUrl)
|
||||
setting.Cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
|
||||
setting.Cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
|
||||
setting.Cfg.Section("server").Key("DOMAIN").SetValue(form.Domain)
|
||||
setting.Cfg.Section("server").Key("ROOT_URL").SetValue(form.AppUrl)
|
||||
|
||||
if len(strings.TrimSpace(form.SmtpHost)) > 0 {
|
||||
setting.Cfg.SetValue("mailer", "ENABLED", "true")
|
||||
setting.Cfg.SetValue("mailer", "HOST", form.SmtpHost)
|
||||
setting.Cfg.SetValue("mailer", "USER", form.SmtpEmail)
|
||||
setting.Cfg.SetValue("mailer", "PASSWD", form.SmtpPasswd)
|
||||
setting.Cfg.Section("mailer").Key("ENABLED").SetValue("true")
|
||||
setting.Cfg.Section("mailer").Key("HOST").SetValue(form.SmtpHost)
|
||||
setting.Cfg.Section("mailer").Key("USER").SetValue(form.SmtpEmail)
|
||||
setting.Cfg.Section("mailer").Key("PASSWD").SetValue(form.SmtpPasswd)
|
||||
|
||||
setting.Cfg.SetValue("service", "REGISTER_EMAIL_CONFIRM", com.ToStr(form.RegisterConfirm == "on"))
|
||||
setting.Cfg.SetValue("service", "ENABLE_NOTIFY_MAIL", com.ToStr(form.MailNotify == "on"))
|
||||
setting.Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(form.RegisterConfirm == "on"))
|
||||
setting.Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(form.MailNotify == "on"))
|
||||
}
|
||||
|
||||
setting.Cfg.SetValue("", "RUN_MODE", "prod")
|
||||
setting.Cfg.Section("").Key("RUN_MODE").SetValue("prod")
|
||||
|
||||
setting.Cfg.SetValue("log", "MODE", "file")
|
||||
setting.Cfg.Section("session").Key("PROVIDER").SetValue("file")
|
||||
|
||||
setting.Cfg.SetValue("security", "INSTALL_LOCK", "true")
|
||||
setting.Cfg.SetValue("security", "SECRET_KEY", base.GetRandomString(15))
|
||||
setting.Cfg.Section("log").Key("MODE").SetValue("file")
|
||||
|
||||
setting.Cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
|
||||
setting.Cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15))
|
||||
|
||||
os.MkdirAll("custom/conf", os.ModePerm)
|
||||
if err := goconfig.SaveConfigFile(setting.Cfg, path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
|
||||
if err := setting.Cfg.SaveTo(path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), INSTALL, &form)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ func Diff(ctx *middleware.Context) {
|
||||
commit := ctx.Repo.Commit
|
||||
commit.CommitMessage = string(base.RenderIssueIndexPattern([]byte(commit.CommitMessage), ctx.Repo.RepoLink))
|
||||
diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName),
|
||||
commitId, setting.MaxGitDiffLines)
|
||||
commitId, setting.Git.MaxGitDiffLines)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "GetDiffCommit", err)
|
||||
return
|
||||
@@ -272,7 +272,7 @@ func CompareDiff(ctx *middleware.Context) {
|
||||
}
|
||||
|
||||
diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitId,
|
||||
afterCommitId, setting.MaxGitDiffLines)
|
||||
afterCommitId, setting.Git.MaxGitDiffLines)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "GetDiffRange", err)
|
||||
return
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"path"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
@@ -169,7 +170,7 @@ func SettingsCollaboration(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||
ctx.Data["PageIsSettingsCollaboration"] = true
|
||||
|
||||
repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/")
|
||||
repoLink := path.Join(ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName)
|
||||
|
||||
if ctx.Req.Method == "POST" {
|
||||
name := strings.ToLower(ctx.Query("collaborator"))
|
||||
|
||||
@@ -98,7 +98,7 @@ func Home(ctx *middleware.Context) {
|
||||
readmeExist := base.IsMarkdownFile(blob.Name()) || base.IsReadmeFile(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = readmeExist
|
||||
if readmeExist {
|
||||
ctx.Data["FileContent"] = string(base.RenderMarkdown(buf, ""))
|
||||
ctx.Data["FileContent"] = string(base.RenderMarkdown(buf, branchLink))
|
||||
} else {
|
||||
if err, content := base.ToUtf8WithErr(buf); err != nil {
|
||||
if err != nil {
|
||||
@@ -154,8 +154,12 @@ func Home(ctx *middleware.Context) {
|
||||
|
||||
// Render issue index links.
|
||||
for _, f := range files {
|
||||
c := f[1].(*git.Commit)
|
||||
c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), ctx.Repo.RepoLink))
|
||||
switch c := f[1].(type) {
|
||||
case *git.Commit:
|
||||
c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), ctx.Repo.RepoLink))
|
||||
case *git.SubModuleFile:
|
||||
c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), ctx.Repo.RepoLink))
|
||||
}
|
||||
}
|
||||
ctx.Data["Files"] = files
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ func SignIn(ctx *middleware.Context) {
|
||||
if err != nil {
|
||||
if err != models.ErrUserNotExist {
|
||||
ctx.Handle(500, "GetUserByName", err)
|
||||
} else {
|
||||
ctx.HTML(200, SIGNIN)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -343,6 +345,27 @@ func Activate(ctx *middleware.Context) {
|
||||
ctx.HTML(200, ACTIVATE)
|
||||
}
|
||||
|
||||
func ActivateEmail(ctx *middleware.Context) {
|
||||
code := ctx.Query("code")
|
||||
email_string := ctx.Query("email")
|
||||
|
||||
// Verify code.
|
||||
if email := models.VerifyActiveEmailCode(code, email_string); email != nil {
|
||||
err := email.Activate()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "ActivateEmail", err)
|
||||
}
|
||||
|
||||
log.Trace("Email activated: %s", email.Email)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.activate_email_success"))
|
||||
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/email")
|
||||
return
|
||||
}
|
||||
|
||||
func ForgotPasswd(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.forgot_password")
|
||||
|
||||
|
||||
@@ -105,6 +105,9 @@ func Dashboard(ctx *middleware.Context) {
|
||||
// FIXME: cache results?
|
||||
u, err := models.GetUserByName(act.ActUserName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
continue
|
||||
}
|
||||
ctx.Handle(500, "GetUserByName", err)
|
||||
return
|
||||
}
|
||||
@@ -198,9 +201,21 @@ func Profile(ctx *middleware.Context) {
|
||||
}
|
||||
feeds := make([]*models.Action, 0, len(actions))
|
||||
for _, act := range actions {
|
||||
if act.IsPrivate {
|
||||
if !ctx.IsSigned {
|
||||
continue
|
||||
}
|
||||
if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
|
||||
models.READABLE); !has {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// FIXME: cache results?
|
||||
u, err := models.GetUserByName(act.ActUserName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
continue
|
||||
}
|
||||
ctx.Handle(500, "GetUserByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/mailer"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
const (
|
||||
SETTINGS_PROFILE base.TplName = "user/settings/profile"
|
||||
SETTINGS_PASSWORD base.TplName = "user/settings/password"
|
||||
SETTINGS_EMAILS base.TplName = "user/settings/email"
|
||||
SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys"
|
||||
SETTINGS_SOCIAL base.TplName = "user/settings/social"
|
||||
SETTINGS_APPLICATIONS base.TplName = "user/settings/applications"
|
||||
@@ -126,6 +128,112 @@ func SettingsAvatar(ctx *middleware.Context, form auth.UploadAvatarForm) {
|
||||
ctx.Flash.Success(ctx.Tr("settings.update_avatar_success"))
|
||||
}
|
||||
|
||||
func SettingsEmails(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["PageIsSettingsEmails"] = true
|
||||
|
||||
var err error
|
||||
ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id)
|
||||
|
||||
if err != nil {
|
||||
ctx.Handle(500, "email.GetEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(200, SETTINGS_EMAILS)
|
||||
}
|
||||
|
||||
func SettingsEmailPost(ctx *middleware.Context, form auth.AddEmailForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["PageIsSettingsEmails"] = true
|
||||
|
||||
var err error
|
||||
ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "email.GetEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete Email address.
|
||||
if ctx.Query("_method") == "DELETE" {
|
||||
id := com.StrTo(ctx.Query("id")).MustInt64()
|
||||
if id <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err = models.DeleteEmailAddress(&models.EmailAddress{Id: id}); err != nil {
|
||||
ctx.Handle(500, "DeleteEmail", err)
|
||||
} else {
|
||||
log.Trace("Email address deleted: %s", ctx.User.Name)
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/email")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Make emailaddress primary.
|
||||
if ctx.Query("_method") == "PRIMARY" {
|
||||
id := com.StrTo(ctx.Query("id")).MustInt64()
|
||||
if id <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err = models.MakeEmailPrimary(&models.EmailAddress{Id: id}); err != nil {
|
||||
ctx.Handle(500, "MakeEmailPrimary", err)
|
||||
} else {
|
||||
log.Trace("Email made primary: %s", ctx.User.Name)
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/email")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add Email address.
|
||||
if ctx.Req.Method == "POST" {
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, SETTINGS_EMAILS)
|
||||
return
|
||||
}
|
||||
|
||||
cleanEmail := strings.Replace(form.Email, "\n", "", -1)
|
||||
e := &models.EmailAddress{
|
||||
Uid: ctx.User.Id,
|
||||
Email: cleanEmail,
|
||||
IsActivated: !setting.Service.RegisterEmailConfirm,
|
||||
}
|
||||
|
||||
if err := models.AddEmailAddress(e); err != nil {
|
||||
if err == models.ErrEmailAlreadyUsed {
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_has_been_used"), SETTINGS_EMAILS, &form)
|
||||
return
|
||||
}
|
||||
ctx.Handle(500, "email.AddEmailAddress", err)
|
||||
return
|
||||
} else {
|
||||
|
||||
// Send confirmation e-mail
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
mailer.SendActivateEmail(ctx.Render, ctx.User, e)
|
||||
|
||||
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
|
||||
log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_email_success_confirmation_email_sent"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
|
||||
}
|
||||
|
||||
log.Trace("Email address added: %s", e.Email)
|
||||
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/email")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx.HTML(200, SETTINGS_EMAILS)
|
||||
}
|
||||
|
||||
func SettingsPassword(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
rm -rf output
|
||||
mkdir output
|
||||
outPath=./output
|
||||
|
||||
rm -rf $outPath
|
||||
mkdir $outPath
|
||||
|
||||
go build ../gogs.go
|
||||
chmod +x gogs
|
||||
mv gogs ./output/
|
||||
cp -r ../conf/ ./output/conf/
|
||||
cp -r ../custom/ ./output/custom/
|
||||
cp -r ./dockerfiles/ ./output/dockerfiles/
|
||||
cp -r ../public/ ./output/public/
|
||||
cp -r ../templates/ ./output/templates/
|
||||
cp ../cert.pem ./output/
|
||||
cp ../CONTRIBUTING.md ./output/
|
||||
cp gogs_supervisord.sh ./output/
|
||||
cp ../key.pem ./output/
|
||||
cp ../LICENSE ./output/
|
||||
cp ../README.md ./output/
|
||||
cp ../README_ZH.md ./output/
|
||||
cp start.bat ./output/
|
||||
cp start.sh ./output/
|
||||
cp ../wercker.yml ./output/
|
||||
cp mysql.sql ./output/
|
||||
mv gogs $outPath/
|
||||
|
||||
cp -r ../conf/ $outPath/conf/
|
||||
cp -r ../custom/ $outPath/custom/
|
||||
cp -r dockerfiles/ $outPath/dockerfiles/
|
||||
cp -r ../public/ $outPath/public/
|
||||
cp -r ../templates/ $outPath/templates/
|
||||
cp ../cert.pem $outPath/
|
||||
cp ../CONTRIBUTING.md $outPath/
|
||||
cp gogs_supervisord.sh $outPath/
|
||||
cp ../key.pem $outPath/
|
||||
cp ../LICENSE $outPath/
|
||||
cp ../README.md $outPath/
|
||||
cp ../README_ZH.md $outPath/
|
||||
cp start.bat $outPath/
|
||||
cp start.sh $outPath/
|
||||
cp ../wercker.yml $outPath/
|
||||
cp mysql.sql $outPath/
|
||||
27
scripts/build_freebsd.sh
Executable file
27
scripts/build_freebsd.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
outPlattform=freebsd
|
||||
outArch=amd64
|
||||
outPath=./output_$outPlattform_$outArch
|
||||
|
||||
rm -rf $outPath
|
||||
mkdir $outPath
|
||||
|
||||
CGO_ENABLED=0 GOOS=$outPlattform GOARCH=$outArch go build ../gogs.go
|
||||
chmod +x gogs
|
||||
mv gogs $outPath/
|
||||
|
||||
cp -r ../conf/ $outPath/conf/
|
||||
cp -r ../custom/ $outPath/custom/
|
||||
cp -r dockerfiles/ $outPath/dockerfiles/
|
||||
cp -r ../public/ $outPath/public/
|
||||
cp -r ../templates/ $outPath/templates/
|
||||
cp ../cert.pem $outPath/
|
||||
cp ../CONTRIBUTING.md $outPath/
|
||||
cp gogs_supervisord.sh $outPath/
|
||||
cp ../key.pem $outPath/
|
||||
cp ../LICENSE $outPath/
|
||||
cp ../README.md $outPath/
|
||||
cp ../README_ZH.md $outPath/
|
||||
cp start.bat $outPath/
|
||||
cp start.sh $outPath/
|
||||
cp ../wercker.yml $outPath/
|
||||
cp mysql.sql $outPath/
|
||||
@@ -1,21 +1,27 @@
|
||||
rm -rf output_linux_64
|
||||
mkdir output_linux_64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ../gogs.go
|
||||
outPlattform=linux
|
||||
outArch=amd64
|
||||
outPath=./output_$outPlattform_$outArch
|
||||
|
||||
rm -rf $outPath
|
||||
mkdir $outPath
|
||||
|
||||
CGO_ENABLED=0 GOOS=$outPlattform GOARCH=$outArch go build ../gogs.go
|
||||
chmod +x gogs
|
||||
mv gogs ./output_linux_64/
|
||||
cp -r ../conf/ ./output_linux_64/conf/
|
||||
cp -r ../custom/ ./output_linux_64/custom/
|
||||
cp -r dockerfiles/ ./output_linux_64/dockerfiles/
|
||||
cp -r ../public/ ./output_linux_64/public/
|
||||
cp -r ../templates/ ./output_linux_64/templates/
|
||||
cp ../cert.pem ./output_linux_64/
|
||||
cp ../CONTRIBUTING.md ./output_linux_64/
|
||||
cp gogs_supervisord.sh ./output_linux_64/
|
||||
cp ../key.pem ./output_linux_64/
|
||||
cp ../LICENSE ./output_linux_64/
|
||||
cp ../README.md ./output_linux_64/
|
||||
cp ../README_ZH.md ./output_linux_64/
|
||||
cp start.bat ./output_linux_64/
|
||||
cp start.sh ./output_linux_64/
|
||||
cp ../wercker.yml ./output_linux_64/
|
||||
cp mysql.sql ./output_linux_64/
|
||||
mv gogs $outPath/
|
||||
|
||||
cp -r ../conf/ $outPath/conf/
|
||||
cp -r ../custom/ $outPath/custom/
|
||||
cp -r dockerfiles/ $outPath/dockerfiles/
|
||||
cp -r ../public/ $outPath/public/
|
||||
cp -r ../templates/ $outPath/templates/
|
||||
cp ../cert.pem $outPath/
|
||||
cp ../CONTRIBUTING.md $outPath/
|
||||
cp gogs_supervisord.sh $outPath/
|
||||
cp ../key.pem $outPath/
|
||||
cp ../LICENSE $outPath/
|
||||
cp ../README.md $outPath/
|
||||
cp ../README_ZH.md $outPath/
|
||||
cp start.bat $outPath/
|
||||
cp start.sh $outPath/
|
||||
cp ../wercker.yml $outPath/
|
||||
cp mysql.sql $outPath/
|
||||
@@ -1 +1 @@
|
||||
0.5.9.1213 Beta
|
||||
0.5.11.0103 Beta
|
||||
@@ -34,8 +34,8 @@
|
||||
<td><a href="{{AppSubUrl}}/admin/auths/{{.Id}}">{{.Name}}</a></td>
|
||||
<td>{{.TypeString}}</td>
|
||||
<td><i class="fa fa{{if .IsActived}}-check{{end}}-square-o"></i></td>
|
||||
<td>{{DateFormat .Updated "M d, Y"}}</td>
|
||||
<td>{{DateFormat .Created "M d, Y"}}</td>
|
||||
<td><span title="{{DateFormat .Updated "r"}}">{{DateFormat .Updated "M d, Y"}}</span></td>
|
||||
<td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td>
|
||||
<td><a href="{{AppSubUrl}}/admin/auths/{{.Id}}"><i class="fa fa-pencil-square-o"></i></a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
@@ -169,13 +169,11 @@
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal admin-dl-horizontal">
|
||||
<dt>{{.i18n.Tr "admin.config.session_provider"}}</dt>
|
||||
<dd>{{.SessionProvider}}</dd>
|
||||
<dd>{{.SessionConfig.Provider}}</dd>
|
||||
<dt>{{.i18n.Tr "admin.config.provider_config"}}</dt>
|
||||
<dd><pre>{{.SessionConfig.ProviderConfig}}</pre></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.cookie_name"}}</dt>
|
||||
<dd>{{.SessionConfig.CookieName}}</dd>
|
||||
<dt>{{.i18n.Tr "admin.config.enable_set_cookie"}}</dt>
|
||||
<dd><i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.gc_interval_time"}}</dt>
|
||||
<dd>{{.SessionConfig.Gclifetime}} {{.i18n.Tr "tool.raw_seconds"}}</dd>
|
||||
<dt>{{.i18n.Tr "admin.config.session_life_time"}}</dt>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<td>{{.NumTeams}}</td>
|
||||
<td>{{.NumMembers}}</td>
|
||||
<td>{{.NumRepos}}</td>
|
||||
<td>{{DateFormat .Created "M d, Y"}}</td>
|
||||
<td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<td>{{.NumWatches}}</td>
|
||||
<td>{{.NumStars}}</td>
|
||||
<td>{{.NumIssues}}</td>
|
||||
<td>{{DateFormat .Created "M d, Y"}}</td>
|
||||
<td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<td><i class="fa fa{{if .IsActive}}-check{{end}}-square-o"></i></td>
|
||||
<td><i class="fa fa{{if .IsAdmin}}-check{{end}}-square-o"></i></td>
|
||||
<td>{{.NumRepos}}</td>
|
||||
<td>{{DateFormat .Created "M d, Y"}}</td>
|
||||
<td><span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span></td>
|
||||
<td><a href="{{AppSubUrl}}/admin/users/{{.Id}}"><i class="fa fa-pencil-square-o"></i></a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
30
templates/mail/auth/activate_email.tmpl
Normal file
30
templates/mail/auth/activate_email.tmpl
Normal file
@@ -0,0 +1,30 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>{{.User.Name}}, please activate your e-mail address</title>
|
||||
</head>
|
||||
<body style="background:#eee;">
|
||||
<div style="color:#333; font:12px/1.5 Tahoma,Arial,sans-serif;; text-shadow:1px 1px #fff; padding:0; margin:0;">
|
||||
<div style="width:600px;margin:0 auto; padding:40px 0 20px;">
|
||||
<div style="border:1px solid #d9d9d9;border-radius:3px; background:#fff; box-shadow: 0px 2px 5px rgba(0, 0, 0,.05); -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0,.05);">
|
||||
<div style="padding: 20px 15px;">
|
||||
<h1 style="font-size:20px; padding:10px 0 20px; margin:0; border-bottom:1px solid #ddd;"><img src="{{.AppUrl}}/img/favicon.png" style="height: 32px; margin-bottom: -10px;"> <a style="color:#333;text-decoration:none;" target="_blank" href="{{.AppUrl}}">{{.AppName}}</a></h1>
|
||||
<div style="padding:40px 15px;">
|
||||
<div style="font-size:16px; padding-bottom:30px; font-weight:bold;">
|
||||
Hi <span style="color: #00BFFF;">{{.User.Name}}</span>,
|
||||
</div>
|
||||
<div style="font-size:14px; padding:0 15px;">
|
||||
<p style="margin:0;padding:0 0 9px 0;">Please click the following link to verify your e-mail address within <b>{{.ActiveCodeLives}} hours</b>.</p>
|
||||
<p style="margin:0;padding:0 0 9px 0;">
|
||||
<a href="{{.AppUrl}}user/activate_email?code={{.Code}}&email={{.Email}}">{{.AppUrl}}user/activate_email?code={{.Code}}&email={{.Email}}</a>
|
||||
</p>
|
||||
<p style="margin:0;padding:0 0 9px 0;">Not working? Try copying and pasting it to your browser.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="color:#aaa;padding:10px;text-align:center;">
|
||||
© 2014 <a style="color:#888;text-decoration:none;" target="_blank" href="http://gogits.org">Gogs: Go Git Service</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,7 +12,7 @@
|
||||
</ul>
|
||||
<form id="label-add-form" action="#" class="form clear hidden">
|
||||
<input type="text" class="ipt" name="name" placeholder="label name" id="label-add-name"/>
|
||||
<div class="inline down drop" id="label-color-drop">
|
||||
<div class="inline down drop label-color-drop">
|
||||
<label for="label-add-color"></label>
|
||||
<input class="ipt" name="color" type="text" placeholder="color" id="label-add-color"/>
|
||||
<div class="drop-down">
|
||||
@@ -30,10 +30,10 @@
|
||||
</div>
|
||||
<ul id="label-list" class="list-no-style">
|
||||
<li class="item" id="label-id">
|
||||
<a class="right" href="#"><i class="octicon octicon-x"></i>Delete</a>
|
||||
<a class="right" href="#"><i class="octicon octicon-pencil"></i>Edit</a>
|
||||
<a class="right delete" href="#"><i class="octicon octicon-x"></i>Delete</a>
|
||||
<a class="right edit" href="#"><i class="octicon octicon-pencil"></i>Edit</a>
|
||||
<a class="right issue-num" href="#"><i class="octicon octicon-issue-opened"></i><strong class="num">12</strong>Issues</a>
|
||||
<a class="left label clear" href="#" style="background-color: red"><i class="octicon octicon-tag"></i><strong>bug</strong></a>
|
||||
<a class="left label clear" href="#" style="background-color: #0052cc" data-color-hex="#0052cc"><i class="octicon octicon-tag"></i><strong>bug</strong></a>
|
||||
</li>
|
||||
<li class="item" id="label-id">
|
||||
<a class="right" href="#"><i class="octicon octicon-x"></i>Delete</a>
|
||||
@@ -45,4 +45,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="label-edit-form-tpl" class="hidden">
|
||||
<li class="item"><form id="label-edit-form" action="#" class="form clear">
|
||||
<input type="text" class="ipt" name="name" placeholder="label name" id="label-edit-name"/>
|
||||
<input type="hidden" name="id" value="id"/>
|
||||
<div class="inline down drop label-color-drop">
|
||||
<label for="label-add-color"></label>
|
||||
<input class="ipt" name="color" type="text" placeholder="color" id="label-edit-color"/>
|
||||
<div class="drop-down">
|
||||
<a href="#" class="color" style="background: red"></a>
|
||||
<a href="#" class="color" style="background: green"></a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-gray right" type="button" id="label-edit-cancel-btn">Cancel</button>
|
||||
<button class="btn btn-green right" id="label-edit-btn">Save Changes</button>
|
||||
</form></li>
|
||||
</div>
|
||||
<div id="label-delete-form-tpl" class="hidden">
|
||||
<li class="item">
|
||||
<form id="label-delete-form" action="#">
|
||||
<input type="hidden" name="id" value="id"/>
|
||||
<span><strong class="text-red">Are you sure?</strong> Deleting a label will remove it from all issues and pull requests.</span>
|
||||
<button class="btn btn-gray right" type="button" id="label-del-cancel-btn">Cancel</button>
|
||||
<button class="btn btn-red right" id="label-del-btn">Delete</button>
|
||||
</form>
|
||||
</li>
|
||||
</div>
|
||||
{{template "ng/base/footer" .}}
|
||||
@@ -22,7 +22,7 @@
|
||||
<i class="fa fa-send fa-2x left"></i>
|
||||
<div class="ssh-content left">
|
||||
<p><strong>{{.Name}}</strong></p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} {{DateFormat .Created "M d, Y"}} — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p>
|
||||
</div>
|
||||
<a href="{{AppSubUrl}}/user/settings/applications?remove={{.Id}}">
|
||||
<button class="btn btn-small btn-red btn-radius ssh-btn right">{{$.i18n.Tr "settings.delete_token"}}</button>
|
||||
|
||||
58
templates/user/settings/email.tmpl
Normal file
58
templates/user/settings/email.tmpl
Normal file
@@ -0,0 +1,58 @@
|
||||
{{template "ng/base/head" .}}
|
||||
{{template "ng/base/header" .}}
|
||||
<div id="setting-wrapper" class="main-wrapper">
|
||||
<div id="user-profile-setting" class="container clear">
|
||||
{{template "user/settings/nav" .}}
|
||||
<div class="grid-4-5 left">
|
||||
<div class="setting-content">
|
||||
{{template "ng/base/alert" .}}
|
||||
<div id="user-email-setting-content">
|
||||
<div id="user-email-panel" class="panel panel-radius">
|
||||
<div class="panel-header">
|
||||
<strong>{{.i18n.Tr "settings.manage_emails"}}</strong>
|
||||
</div>
|
||||
<ul class="panel-body setting-list">
|
||||
<li>{{.i18n.Tr "settings.email_desc"}}</li>
|
||||
{{range .Emails}}
|
||||
<li class="email clear">
|
||||
<div class="email-content left">
|
||||
<p><strong>{{.Email}}</strong> {{if .IsPrimary}} <span class="email-primary">{{$.i18n.Tr "settings.primary"}}</span> {{end}}</p>
|
||||
</div>
|
||||
{{if not .IsPrimary}}
|
||||
{{if .IsActivated}}
|
||||
<form action="{{AppSubUrl}}/user/settings/email" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input name="_method" type="hidden" value="PRIMARY">
|
||||
<input name="id" type="hidden" value="{{.Id}}">
|
||||
<button class="right email-btn btn btn-green btn-radius btn-small">{{$.i18n.Tr "settings.primary_email"}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form action="{{AppSubUrl}}/user/settings/email" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input name="_method" type="hidden" value="DELETE">
|
||||
<input name="id" type="hidden" value="{{.Id}}">
|
||||
<button class="right email-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_email"}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
<form action="{{AppSubUrl}}/user/settings/email" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<p class="panel-header"><strong>{{.i18n.Tr "settings.add_new_email"}}</strong></p>
|
||||
<p class="field">
|
||||
<label class="req" for="email">{{.i18n.Tr "email"}}</label>
|
||||
<input class="ipt ipt-radius" id="email" name="email" type="text" required />
|
||||
</p>
|
||||
<p class="field">
|
||||
<label></label>
|
||||
<button class="btn btn-green btn-radius" id="email-add-btn">{{.i18n.Tr "settings.add_email"}}</button>
|
||||
</p>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "ng/base/footer" .}}
|
||||
@@ -4,6 +4,7 @@
|
||||
<ul class="menu menu-vertical switching-list grid-1-5 left">
|
||||
<li {{if .PageIsSettingsProfile}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings">{{.i18n.Tr "settings.profile"}}</a></li>
|
||||
<li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li>
|
||||
<li {{if .PageIsSettingsEmail}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li>
|
||||
<li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li>
|
||||
<li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li>
|
||||
<li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="ssh-content left">
|
||||
<p><strong>{{Oauth2Name .Type}}</strong></p>
|
||||
<p class="print">{{.Identity}}</p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} {{DateFormat .Created "M d, Y"}} — <i class="octicon octicon-info"></i>{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}</i></p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}</i></p>
|
||||
</div>
|
||||
<a class="right btn btn-small btn-red btn-header btn-radius" href="{{AppSubUrl}}/user/settings/social?remove={{.Id}}">{{$.i18n.Tr "settings.unbind"}}</a>
|
||||
</li>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="ssh-content left">
|
||||
<p><strong>{{.Name}}</strong></p>
|
||||
<p class="print">{{.Fingerprint}}</p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} {{DateFormat .Created "M d, Y"}} — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p>
|
||||
<p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} <span title="{{DateFormat .Created "r"}}">{{DateFormat .Created "M d, Y"}}</span> — <i class="octicon octicon-info"></i>{{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span title="{{DateFormat .Updated "r"}}">{{DateFormat .Updated "M d, Y"}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i></p>
|
||||
</div>
|
||||
<form action="{{AppSubUrl}}/user/settings/ssh" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
|
||||
Reference in New Issue
Block a user