Skip to content

Commit fa7233d

Browse files
authored
Merge branch 'main' into clear-pr
2 parents d841de8 + d845be6 commit fa7233d

File tree

13 files changed

+109
-56
lines changed

13 files changed

+109
-56
lines changed

models/auth/token_scope.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,23 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{
168168

169169
// Parse parses the scope string into a bitmap, thus removing possible duplicates.
170170
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
171-
list := strings.Split(string(s), ",")
172-
173171
var bitmap AccessTokenScopeBitmap
174-
for _, v := range list {
172+
173+
// The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
174+
remainingScopes := string(s)
175+
for len(remainingScopes) > 0 {
176+
i := strings.IndexByte(remainingScopes, ',')
177+
var v string
178+
if i < 0 {
179+
v = remainingScopes
180+
remainingScopes = ""
181+
} else if i+1 >= len(remainingScopes) {
182+
v = remainingScopes[:i]
183+
remainingScopes = ""
184+
} else {
185+
v = remainingScopes[:i]
186+
remainingScopes = remainingScopes[i+1:]
187+
}
175188
singleScope := AccessTokenScope(v)
176189
if singleScope == "" {
177190
continue
@@ -187,9 +200,15 @@ func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
187200
}
188201
bitmap |= bits
189202
}
203+
190204
return bitmap, nil
191205
}
192206

207+
// StringSlice returns the AccessTokenScope as a []string
208+
func (s AccessTokenScope) StringSlice() []string {
209+
return strings.Split(string(s), ",")
210+
}
211+
193212
// Normalize returns a normalized scope string without any duplicates.
194213
func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
195214
bitmap, err := s.Parse()

modules/setting/config_provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interfac
2525
}
2626
}
2727

28-
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey string) {
28+
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
2929
if rootCfg.Section(oldSection).HasKey(oldKey) {
30-
log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be removed in v1.19.0", oldSection, oldKey, newSection, newKey)
30+
log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
3131
}
3232
}
3333

modules/setting/indexer.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
5656
Indexer.IssueIndexerName = sec.Key("ISSUE_INDEXER_NAME").MustString(Indexer.IssueIndexerName)
5757

5858
// The following settings are deprecated and can be overridden by settings in [queue] or [queue.issue_indexer]
59-
// FIXME: DEPRECATED to be removed in v1.18.0
60-
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_TYPE", "queue.issue_indexer", "TYPE")
61-
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_DIR", "queue.issue_indexer", "DATADIR")
62-
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_CONN_STR", "queue.issue_indexer", "CONN_STR")
63-
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_BATCH_NUMBER", "queue.issue_indexer", "BATCH_LENGTH")
64-
deprecatedSetting(rootCfg, "indexer", "UPDATE_BUFFER_LEN", "queue.issue_indexer", "LENGTH")
59+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
60+
// if these are removed, the warning will not be shown
61+
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_TYPE", "queue.issue_indexer", "TYPE", "v1.19.0")
62+
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_DIR", "queue.issue_indexer", "DATADIR", "v1.19.0")
63+
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_CONN_STR", "queue.issue_indexer", "CONN_STR", "v1.19.0")
64+
deprecatedSetting(rootCfg, "indexer", "ISSUE_INDEXER_QUEUE_BATCH_NUMBER", "queue.issue_indexer", "BATCH_LENGTH", "v1.19.0")
65+
deprecatedSetting(rootCfg, "indexer", "UPDATE_BUFFER_LEN", "queue.issue_indexer", "LENGTH", "v1.19.0")
6566

6667
Indexer.RepoIndexerEnabled = sec.Key("REPO_INDEXER_ENABLED").MustBool(false)
6768
Indexer.RepoType = sec.Key("REPO_INDEXER_TYPE").MustString("bleve")

modules/setting/lfs.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ func loadLFSFrom(rootCfg ConfigProvider) {
3535
storageType := lfsSec.Key("STORAGE_TYPE").MustString("")
3636

3737
// Specifically default PATH to LFS_CONTENT_PATH
38-
// FIXME: DEPRECATED to be removed in v1.18.0
39-
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH")
38+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
39+
// if these are removed, the warning will not be shown
40+
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
4041
lfsSec.Key("PATH").MustString(
4142
sec.Key("LFS_CONTENT_PATH").String())
4243

modules/setting/mailer.go

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@ func loadMailerFrom(rootCfg ConfigProvider) {
6464
}
6565

6666
// Handle Deprecations and map on to new configuration
67-
// FIXME: DEPRECATED to be removed in v1.19.0
68-
deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL")
67+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
68+
// if these are removed, the warning will not be shown
69+
deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0")
6970
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
7071
if sec.Key("MAILER_TYPE").String() == "sendmail" {
7172
sec.Key("PROTOCOL").MustString("sendmail")
7273
}
7374
}
7475

75-
// FIXME: DEPRECATED to be removed in v1.19.0
76-
deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR")
76+
deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0")
7777
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
7878
givenHost := sec.Key("HOST").String()
7979
addr, port, err := net.SplitHostPort(givenHost)
@@ -89,8 +89,7 @@ func loadMailerFrom(rootCfg ConfigProvider) {
8989
sec.Key("SMTP_PORT").MustString(port)
9090
}
9191

92-
// FIXME: DEPRECATED to be removed in v1.19.0
93-
deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
92+
deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0")
9493
if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
9594
if sec.Key("IS_TLS_ENABLED").MustBool() {
9695
sec.Key("PROTOCOL").MustString("smtps")
@@ -99,38 +98,32 @@ func loadMailerFrom(rootCfg ConfigProvider) {
9998
}
10099
}
101100

102-
// FIXME: DEPRECATED to be removed in v1.19.0
103-
deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO")
101+
deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0")
104102
if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
105103
sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool())
106104
}
107105

108-
// FIXME: DEPRECATED to be removed in v1.19.0
109-
deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT")
106+
deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0")
110107
if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
111108
sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool())
112109
}
113110

114-
// FIXME: DEPRECATED to be removed in v1.19.0
115-
deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT")
111+
deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0")
116112
if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
117113
sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool())
118114
}
119115

120-
// FIXME: DEPRECATED to be removed in v1.19.0
121-
deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE")
116+
deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0")
122117
if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
123118
sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String())
124119
}
125120

126-
// FIXME: DEPRECATED to be removed in v1.19.0
127-
deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE")
121+
deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0")
128122
if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
129123
sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String())
130124
}
131125

132-
// FIXME: DEPRECATED to be removed in v1.19.0
133-
deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT")
126+
deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0")
134127
if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
135128
sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false))
136129
}

modules/setting/mirror.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ var Mirror = struct {
2727
func loadMirrorFrom(rootCfg ConfigProvider) {
2828
// Handle old configuration through `[repository]` `DISABLE_MIRRORS`
2929
// - please note this was badly named and only disabled the creation of new pull mirrors
30-
// FIXME: DEPRECATED to be removed in v1.18.0
31-
deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED")
30+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
31+
// if these are removed, the warning will not be shown
32+
deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED", "v1.19.0")
3233
if rootCfg.Section("repository").Key("DISABLE_MIRRORS").MustBool(false) {
3334
Mirror.DisableNewPull = true
3435
}

modules/setting/server.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,38 +178,40 @@ func loadServerFrom(rootCfg ConfigProvider) {
178178
switch protocolCfg {
179179
case "https":
180180
Protocol = HTTPS
181-
// FIXME: DEPRECATED to be removed in v1.18.0
181+
182+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
183+
// if these are removed, the warning will not be shown
182184
if sec.HasKey("ENABLE_ACME") {
183185
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
184186
} else {
185-
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME")
187+
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
186188
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
187189
}
188190
if EnableAcme {
189191
AcmeURL = sec.Key("ACME_URL").MustString("")
190192
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
191-
// FIXME: DEPRECATED to be removed in v1.18.0
193+
192194
if sec.HasKey("ACME_ACCEPTTOS") {
193195
AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false)
194196
} else {
195-
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS")
197+
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0")
196198
AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
197199
}
198200
if !AcmeTOS {
199201
log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).")
200202
}
201-
// FIXME: DEPRECATED to be removed in v1.18.0
203+
202204
if sec.HasKey("ACME_DIRECTORY") {
203205
AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https")
204206
} else {
205-
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY")
207+
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0")
206208
AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
207209
}
208-
// FIXME: DEPRECATED to be removed in v1.18.0
210+
209211
if sec.HasKey("ACME_EMAIL") {
210212
AcmeEmail = sec.Key("ACME_EMAIL").MustString("")
211213
} else {
212-
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL")
214+
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
213215
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
214216
}
215217
} else {

modules/setting/task.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33

44
package setting
55

6-
// FIXME: DEPRECATED to be removed in v1.18.0
6+
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
7+
// if these are removed, the warning will not be shown
78
// - will need to set default for [queue.task] LENGTH to 1000 though
89
func loadTaskFrom(rootCfg ConfigProvider) {
910
taskSec := rootCfg.Section("task")
1011
queueTaskSec := rootCfg.Section("queue.task")
1112

12-
deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE")
13-
deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR")
14-
deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH")
13+
deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE", "v1.19.0")
14+
deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR", "v1.19.0")
15+
deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH", "v1.19.0")
1516

1617
switch taskSec.Key("QUEUE_TYPE").MustString("channel") {
1718
case "channel":

modules/structs/user_app.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@ import (
1111
// AccessToken represents an API access token.
1212
// swagger:response AccessToken
1313
type AccessToken struct {
14-
ID int64 `json:"id"`
15-
Name string `json:"name"`
16-
Token string `json:"sha1"`
17-
TokenLastEight string `json:"token_last_eight"`
14+
ID int64 `json:"id"`
15+
Name string `json:"name"`
16+
Token string `json:"sha1"`
17+
TokenLastEight string `json:"token_last_eight"`
18+
Scopes []string `json:"scopes"`
1819
}
1920

2021
// AccessTokenList represents a list of API access token.
2122
// swagger:response AccessTokenList
2223
type AccessTokenList []*AccessToken
2324

2425
// CreateAccessTokenOption options when create access token
25-
// swagger:parameters userCreateToken
2626
type CreateAccessTokenOption struct {
27-
Name string `json:"name" binding:"Required"`
27+
// required: true
28+
Name string `json:"name" binding:"Required"`
29+
Scopes []string `json:"scopes"`
2830
}
2931

3032
// CreateOAuth2ApplicationOptions holds options to create an oauth2 application

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ access_token_deletion_confirm_action = Delete
757757
access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue?
758758
delete_token_success = The token has been deleted. Applications using it no longer have access to your account.
759759
select_scopes = Select scopes
760+
scopes_list = Scopes:
760761
761762
manage_oauth2_applications = Manage OAuth2 Applications
762763
edit_oauth2_application = Edit OAuth2 Application

routers/api/v1/user/app.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"net/http"
1111
"strconv"
12+
"strings"
1213

1314
auth_model "code.gitea.io/gitea/models/auth"
1415
"code.gitea.io/gitea/modules/context"
@@ -62,6 +63,7 @@ func ListAccessTokens(ctx *context.APIContext) {
6263
ID: tokens[i].ID,
6364
Name: tokens[i].Name,
6465
TokenLastEight: tokens[i].TokenLastEight,
66+
Scopes: tokens[i].Scope.StringSlice(),
6567
}
6668
}
6769

@@ -82,9 +84,9 @@ func CreateAccessToken(ctx *context.APIContext) {
8284
// - name: username
8385
// in: path
8486
// description: username of user
85-
// type: string
8687
// required: true
87-
// - name: userCreateToken
88+
// type: string
89+
// - name: body
8890
// in: body
8991
// schema:
9092
// "$ref": "#/definitions/CreateAccessTokenOption"
@@ -111,6 +113,13 @@ func CreateAccessToken(ctx *context.APIContext) {
111113
return
112114
}
113115

116+
scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize()
117+
if err != nil {
118+
ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
119+
return
120+
}
121+
t.Scope = scope
122+
114123
if err := auth_model.NewAccessToken(t); err != nil {
115124
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
116125
return

templates/swagger/v1_json.tmpl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14084,14 +14084,13 @@
1408414084
"parameters": [
1408514085
{
1408614086
"type": "string",
14087-
"x-go-name": "Name",
1408814087
"description": "username of user",
1408914088
"name": "username",
1409014089
"in": "path",
1409114090
"required": true
1409214091
},
1409314092
{
14094-
"name": "userCreateToken",
14093+
"name": "body",
1409514094
"in": "body",
1409614095
"schema": {
1409714096
"$ref": "#/definitions/CreateAccessTokenOption"
@@ -14194,6 +14193,13 @@
1419414193
"type": "string",
1419514194
"x-go-name": "Name"
1419614195
},
14196+
"scopes": {
14197+
"type": "array",
14198+
"items": {
14199+
"type": "string"
14200+
},
14201+
"x-go-name": "Scopes"
14202+
},
1419714203
"sha1": {
1419814204
"type": "string",
1419914205
"x-go-name": "Token"
@@ -14925,10 +14931,20 @@
1492514931
"CreateAccessTokenOption": {
1492614932
"description": "CreateAccessTokenOption options when create access token",
1492714933
"type": "object",
14934+
"required": [
14935+
"name"
14936+
],
1492814937
"properties": {
1492914938
"name": {
1493014939
"type": "string",
1493114940
"x-go-name": "Name"
14941+
},
14942+
"scopes": {
14943+
"type": "array",
14944+
"items": {
14945+
"type": "string"
14946+
},
14947+
"x-go-name": "Scopes"
1493214948
}
1493314949
},
1493414950
"x-go-package": "code.gitea.io/gitea/modules/structs"

templates/user/settings/applications.tmpl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@
2121
</div>
2222
<i class="icon tooltip{{if .HasRecentActivity}} green{{end}}" {{if .HasRecentActivity}}data-content="{{$.locale.Tr "settings.token_state_desc"}}"{{end}}>{{svg "fontawesome-send" 36}}</i>
2323
<div class="content">
24-
<strong>{{.Name}}</strong>
24+
<details><summary><strong>{{.Name}}</strong></summary>
25+
<p class="gt-my-2">{{$.locale.Tr "settings.scopes_list"}}</p>
26+
<ul class="gt-my-2">
27+
{{range .Scope.StringSlice}}
28+
<li>{{.}}</li>
29+
{{end}}
30+
</ul>
31+
</details>
2532
<div class="activity meta">
2633
<i>{{$.locale.Tr "settings.add_on"}} <span><time data-format="short-date" datetime="{{.CreatedUnix.FormatLong}}">{{.CreatedUnix.FormatShort}}</time></span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}><time data-format="short-date" datetime="{{.UpdatedUnix.FormatLong}}">{{.UpdatedUnix.FormatShort}}</time></span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i>
2734
</div>

0 commit comments

Comments
 (0)