diff --git a/models/licenses/license_key.go b/models/licenses/license_key.go index 44f3cddcaa..827ff5ddf2 100644 --- a/models/licenses/license_key.go +++ b/models/licenses/license_key.go @@ -77,6 +77,19 @@ func CreateLicenseKey(ctx context.Context, key *LicenseKey) (rawKey string, err return rawKey, nil } +// CreateLicenseKeyCustom stores a key with a user-provided raw key string. +// The raw key is hashed and stored — it will not be recoverable after this. +func CreateLicenseKeyCustom(ctx context.Context, key *LicenseKey, rawKey string) error { + key.KeyHash = HashKey(rawKey) + if len(rawKey) > 12 { + key.KeyPrefix = rawKey[:12] + "..." + } else { + key.KeyPrefix = rawKey + } + _, err := db.GetEngine(ctx).Insert(key) + return err +} + // GetLicenseKeyByHash looks up a key by its SHA-256 hash. func GetLicenseKeyByHash(ctx context.Context, hash string) (*LicenseKey, error) { key := new(LicenseKey) @@ -160,6 +173,8 @@ func DeleteLicenseKey(ctx context.Context, id int64) error { // Returns the key record and its associated package, or an error. // The domain parameter is optional — when provided, it is checked against // the key's DomainRestriction list and the MaxSites limit. +// On first heartbeat with a domain, if no DomainRestriction is set, the domain +// is automatically associated as the key's restriction (lock-on-first-use). func ValidateLicenseKey(ctx context.Context, rawKey, domain string) (*LicenseKey, *LicensePackage, error) { hash := HashKey(rawKey) key, err := GetLicenseKeyByHash(ctx, hash) @@ -201,6 +216,32 @@ func ValidateLicenseKey(ctx context.Context, rawKey, domain string) (*LicenseKey if !allowed { return nil, nil, fmt.Errorf("domain not allowed for this license key") } + } else { + // No domain restriction set — auto-associate on first heartbeat. + // Append this domain to the restriction list, enforcing max_sites. + maxSites := key.MaxSites + if maxSites == 0 { + maxSites = pkg.MaxSites + } + domainKnown, _ := IsDomainKnownForKey(ctx, key.ID, domain) + if !domainKnown { + if maxSites > 0 { + uniqueDomains, err := CountUniqueDomainsByKey(ctx, key.ID) + if err != nil { + return nil, nil, fmt.Errorf("failed to count domains: %w", err) + } + if uniqueDomains >= int64(maxSites) { + return nil, nil, fmt.Errorf("site limit reached (%d/%d)", uniqueDomains, maxSites) + } + } + // Append this domain to the key's restriction list. + _ = updateDomainRestriction(ctx, key.ID, domain) + if key.DomainRestriction == "" { + key.DomainRestriction = domain + } else { + key.DomainRestriction = key.DomainRestriction + "," + domain + } + } } // Site limit check: use key's MaxSites, fall back to package default. @@ -223,3 +264,42 @@ func ValidateLicenseKey(ctx context.Context, rawKey, domain string) (*LicenseKey return key, pkg, nil } + +// updateDomainRestriction appends a domain to a key's DomainRestriction field in the DB. +func updateDomainRestriction(ctx context.Context, keyID int64, domain string) error { + key, err := GetLicenseKeyByID(ctx, keyID) + if err != nil { + return err + } + if key.DomainRestriction == "" { + key.DomainRestriction = domain + } else { + key.DomainRestriction = key.DomainRestriction + "," + domain + } + _, err = db.GetEngine(ctx).ID(keyID).Cols("domain_restriction").Update(key) + return err +} + +// RenewLicenseKey extends the expiration of a key by the given number of days +// from the current expiry (or from now if already expired/no expiry set). +func RenewLicenseKey(ctx context.Context, keyID int64, days int) error { + key, err := GetLicenseKeyByID(ctx, keyID) + if err != nil { + return err + } + + now := timeutil.TimeStampNow() + var base timeutil.TimeStamp + if key.ExpiresUnix > 0 && key.ExpiresUnix > now { + // Key still valid — extend from current expiry. + base = key.ExpiresUnix + } else { + // Key expired or has no expiry — extend from now. + base = now + } + + key.ExpiresUnix = base + timeutil.TimeStamp(int64(days)*86400) + key.IsActive = true + _, err = db.GetEngine(ctx).ID(keyID).Cols("expires_unix", "is_active").Update(key) + return err +} diff --git a/models/licenses/license_package.go b/models/licenses/license_package.go index 24135b9b7c..52941d5956 100644 --- a/models/licenses/license_package.go +++ b/models/licenses/license_package.go @@ -83,6 +83,11 @@ func UpdateLicensePackage(ctx context.Context, pkg *LicensePackage) error { return err } +// CountOrgPackages returns the number of license packages for an organization. +func CountOrgPackages(ctx context.Context, orgID int64) (int64, error) { + return db.GetEngine(ctx).Where("owner_id = ?", orgID).Count(new(LicensePackage)) +} + // DeleteLicensePackage deletes a license package by ID. func DeleteLicensePackage(ctx context.Context, id int64) error { _, err := db.GetEngine(ctx).ID(id).Delete(new(LicensePackage)) diff --git a/models/unit/unit.go b/models/unit/unit.go index 1dd0013d19..0c555b0d7d 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -33,9 +33,7 @@ const ( TypeProjects // 8 Projects TypePackages // 9 Packages TypeActions // 10 Actions - - // FIXME: TEAM-UNIT-PERMISSION: the team unit "admin" permission's design is not right, when a new unit is added in the future, - // admin team won't inherit the correct admin permission for the new unit, need to have a complete fix before adding any new unit. + TypeLicenses // 11 Licenses ) // Value returns integer value for unit type (used by template) @@ -65,6 +63,7 @@ var ( TypeProjects, TypePackages, TypeActions, + TypeLicenses, } // DefaultRepoUnits contains the default unit types @@ -328,6 +327,15 @@ var ( perm.AccessModeOwner, } + UnitLicenses = Unit{ + TypeLicenses, + "repo.licenses", + "/licenses", + "repo.licenses.desc", + 8, + perm.AccessModeOwner, + } + // Units contains all the units Units = map[Type]Unit{ TypeCode: UnitCode, @@ -340,6 +348,7 @@ var ( TypeProjects: UnitProjects, TypePackages: UnitPackages, TypeActions: UnitActions, + TypeLicenses: UnitLicenses, } ) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 5d6d905fda..1ee6f8c4b5 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2635,7 +2635,7 @@ "repo.licenses.active": "Active", "repo.licenses.inactive": "Inactive", "repo.licenses.none": "No License Packages", - "repo.licenses.none_desc": "License packages can be created via the API to gate access to update streams.", + "repo.licenses.none_desc": "Create a license package to start managing keys and gating update feeds.", "repo.licenses.issued_keys": "Issued Keys", "repo.licenses.key_prefix": "Key", "repo.licenses.licensee": "Licensee", @@ -2650,7 +2650,7 @@ "repo.licenses.package_created": "License package created successfully.", "repo.licenses.generate_key": "Generate Key", "repo.licenses.key_created": "License Key Created", - "repo.licenses.key_created_copy": "Copy this key now. It will not be shown again.", + "repo.licenses.key_created_copy": "This key is hashed before storage and cannot be retrieved later. Copy and store it securely now.", "repo.licenses.revoke": "Revoke", "repo.licenses.edit_package": "Edit License Package", "repo.licenses.delete_package": "Delete Package", @@ -2670,6 +2670,22 @@ "repo.licenses.expires_at_help": "Leave empty for no expiry (lifetime).", "repo.licenses.key_updated": "License key updated.", "repo.licenses.last_seen": "Last Seen", + "repo.licenses.confirm_delete_package": "Delete this package? This action cannot be undone.", + "repo.licenses.confirm_revoke_key": "Revoke this license key? The licensee will immediately lose access to update feeds.", + "repo.licenses.feed_joomla_xml": "Joomla XML", + "repo.licenses.feed_dolibarr_json": "Dolibarr JSON", + "repo.licenses.feed_joomla_updates": "Joomla updates.xml", + "repo.licenses.feed_dolibarr_updates": "Dolibarr JSON", + "repo.licenses.master_label": "Master", + "repo.licenses.unlimited": "unlimited", + "repo.licenses.active_help_package": "Deactivating blocks new key creation and disables all issued keys.", + "repo.licenses.active_help_key": "Deactivating immediately blocks update feed access for this licensee.", + "repo.licenses.renew": "Renew", + "repo.licenses.key_renewed": "License key renewed for %d days.", + "repo.licenses.confirm_renew_key": "Renew this license key? The expiration will be extended by the package duration.", + "repo.licenses.desc": "License packages and keys for gating update feeds.", + "repo.licenses.custom_key_placeholder": "Custom key (optional)", + "repo.licenses.custom_key_help": "Leave empty to auto-generate. Site admins and org owners can set a custom key value.", "repo.release.draft": "Draft", "repo.release.prerelease": "Pre-Release", "repo.release.stable": "Stable", diff --git a/routers/web/org/home.go b/routers/web/org/home.go index fd25172ab0..d91927df29 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -111,6 +111,10 @@ func home(ctx *context.Context, viewRepositories bool) { orgCfg, _ := licenses_model.GetOrgConfig(ctx, ctx.Org.Organization.ID) ctx.Data["OrgLicensingEnabled"] = orgCfg != nil && orgCfg.LicensingEnabled + if orgCfg != nil && orgCfg.LicensingEnabled { + numPkgs, _ := licenses_model.CountOrgPackages(ctx, ctx.Org.Organization.ID) + ctx.Data["NumOrgLicensePackages"] = numPkgs + } ctx.Data["IsPublicMember"] = func(uid int64) bool { return membersIsPublic[uid] } diff --git a/routers/web/org/licenses.go b/routers/web/org/licenses.go index 1feb9bc988..1ab78a5dac 100644 --- a/routers/web/org/licenses.go +++ b/routers/web/org/licenses.go @@ -10,6 +10,8 @@ import ( "time" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses" + "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm" + unit_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" @@ -54,8 +56,10 @@ func Licenses(ctx *context.Context) { org := ctx.Org.Organization ownerID := org.ID - // Auto-create master key if org owner. - if ctx.Org.IsOwner { + canWriteLicenses := ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unit_model.TypeLicenses) >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() + + // Auto-create master key if has write access. + if canWriteLicenses { newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID) if err != nil { ctx.ServerError("EnsureMasterKey", err) @@ -89,7 +93,7 @@ func Licenses(ctx *context.Context) { return } ctx.Data["LicenseKeys"] = keys - ctx.Data["IsRepoAdmin"] = ctx.Org.IsOwner + ctx.Data["IsRepoAdmin"] = canWriteLicenses ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin() ctx.Data["OrgLicensingEnabled"] = true @@ -168,10 +172,21 @@ func LicensesGenerateKey(ctx *context.Context) { key.ExpiresUnix = timeutil.TimeStamp(expires.Unix()) } - rawKey, err := licenses.CreateLicenseKey(ctx, key) - if err != nil { - ctx.ServerError("CreateLicenseKey", err) - return + // Site admins and org owners can manually set a custom key. + var rawKey string + customKey := strings.TrimSpace(ctx.FormString("custom_key")) + if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Org.IsOwner) { + if err := licenses.CreateLicenseKeyCustom(ctx, key, customKey); err != nil { + ctx.ServerError("CreateLicenseKeyCustom", err) + return + } + rawKey = customKey + } else { + rawKey, err = licenses.CreateLicenseKey(ctx, key) + if err != nil { + ctx.ServerError("CreateLicenseKey", err) + return + } } // Re-render with the new key shown. @@ -392,3 +407,32 @@ func LicensesRevokeKey(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.licenses.key_revoked")) ctx.Redirect(ctx.Org.OrgLink + "/-/licenses") } + +// LicensesRenewKey extends a license key's expiration by the package's duration. +func LicensesRenewKey(ctx *context.Context) { + keyID := ctx.PathParamInt64("id") + key, err := licenses.GetLicenseKeyByID(ctx, keyID) + if err != nil { + ctx.ServerError("GetLicenseKeyByID", err) + return + } + + pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID) + if err != nil { + ctx.ServerError("GetLicensePackageByID", err) + return + } + + days := pkg.DurationDays + if days == 0 { + days = 365 // default to 1 year for lifetime packages + } + + if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil { + ctx.ServerError("RenewLicenseKey", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.licenses.key_renewed", days)) + ctx.Redirect(ctx.Org.OrgLink + "/-/licenses") +} diff --git a/routers/web/repo/licenses.go b/routers/web/repo/licenses.go index 7602342a3a..d9d32dfe9f 100644 --- a/routers/web/repo/licenses.go +++ b/routers/web/repo/licenses.go @@ -10,6 +10,7 @@ import ( "time" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses" + unit_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates" "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" @@ -51,13 +52,14 @@ func Licenses(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.licenses") ctx.Data["PageIsLicenses"] = true ctx.Data["IsLicensesPage"] = true - ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.IsAdmin() + canWriteLicenses := ctx.Repo.Permission.CanWrite(unit_model.TypeLicenses) || ctx.IsUserSiteAdmin() + ctx.Data["IsRepoAdmin"] = canWriteLicenses ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin() ownerID := ctx.Repo.Repository.OwnerID // Auto-create master package + key if admin and none exist. - if ctx.Repo.Permission.IsAdmin() { + if canWriteLicenses { newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID) if err != nil { ctx.ServerError("EnsureMasterKey", err) @@ -169,16 +171,27 @@ func LicensesGenerateKey(ctx *context.Context) { key.ExpiresUnix = timeutil.TimeStamp(expires.Unix()) } - rawKey, err := licenses.CreateLicenseKey(ctx, key) - if err != nil { - ctx.ServerError("CreateLicenseKey", err) - return + // Site admins and org owners can manually set a custom key. + var rawKey string + customKey := strings.TrimSpace(ctx.FormString("custom_key")) + if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Repo.Permission.IsOwner()) { + if err := licenses.CreateLicenseKeyCustom(ctx, key, customKey); err != nil { + ctx.ServerError("CreateLicenseKeyCustom", err) + return + } + rawKey = customKey + } else { + rawKey, err = licenses.CreateLicenseKey(ctx, key) + if err != nil { + ctx.ServerError("CreateLicenseKey", err) + return + } } ctx.Data["Title"] = ctx.Tr("repo.licenses") ctx.Data["PageIsLicenses"] = true ctx.Data["IsLicensesPage"] = true - ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.IsAdmin() + ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.CanWrite(unit_model.TypeLicenses) || ctx.IsUserSiteAdmin() ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin() ctx.Data["NewKeyCreated"] = rawKey @@ -398,3 +411,32 @@ func LicensesDeletePackage(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.licenses.package_deleted")) ctx.Redirect(ctx.Repo.RepoLink + "/licenses") } + +// LicensesRenewKey extends a license key's expiration by the package's duration. +func LicensesRenewKey(ctx *context.Context) { + keyID := ctx.PathParamInt64("id") + key, err := licenses.GetLicenseKeyByID(ctx, keyID) + if err != nil { + ctx.ServerError("GetLicenseKeyByID", err) + return + } + + pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID) + if err != nil { + ctx.ServerError("GetLicensePackageByID", err) + return + } + + days := pkg.DurationDays + if days == 0 { + days = 365 // default to 1 year for lifetime packages + } + + if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil { + ctx.ServerError("RenewLicenseKey", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.licenses.key_renewed", days)) + ctx.Redirect(ctx.Repo.RepoLink + "/licenses") +} diff --git a/routers/web/web.go b/routers/web/web.go index a1cb3cb32c..6c2ce3907c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1106,15 +1106,18 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Group("/licenses", func() { m.Get("", org.Licenses) - m.Post("/packages", org.LicensesCreatePackage) - m.Get("/packages/{id}/edit", org.LicensesEditPackage) - m.Post("/packages/{id}/edit", org.LicensesEditPackagePost) - m.Post("/packages/{id}/delete", org.LicensesDeletePackage) - m.Post("/keys/generate", org.LicensesGenerateKey) - m.Get("/keys/{id}/edit", org.LicensesEditKey) - m.Post("/keys/{id}/edit", org.LicensesEditKeyPost) - m.Post("/keys/{id}/revoke", org.LicensesRevokeKey) - }) + m.Group("", func() { + m.Post("/packages", org.LicensesCreatePackage) + m.Get("/packages/{id}/edit", org.LicensesEditPackage) + m.Post("/packages/{id}/edit", org.LicensesEditPackagePost) + m.Post("/packages/{id}/delete", org.LicensesDeletePackage) + m.Post("/keys/generate", org.LicensesGenerateKey) + m.Get("/keys/{id}/edit", org.LicensesEditKey) + m.Post("/keys/{id}/edit", org.LicensesEditKeyPost) + m.Post("/keys/{id}/revoke", org.LicensesRevokeKey) + m.Post("/keys/{id}/renew", org.LicensesRenewKey) + }, reqUnitAccess(unit.TypeLicenses, perm.AccessModeWrite, true)) + }, reqUnitAccess(unit.TypeLicenses, perm.AccessModeRead, true)) m.Get("/repositories", org.Repositories) m.Get("/heatmap", user.DashboardHeatmap) @@ -1521,15 +1524,18 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { // "/{username}/{reponame}": licenses page m.Group("/{username}/{reponame}/licenses", func() { m.Get("", repo.Licenses) - m.Post("/packages", repo.LicensesCreatePackage) - m.Get("/packages/{id}/edit", repo.LicensesEditPackage) - m.Post("/packages/{id}/edit", repo.LicensesEditPackagePost) - m.Post("/packages/{id}/delete", repo.LicensesDeletePackage) - m.Post("/keys/generate", repo.LicensesGenerateKey) - m.Get("/keys/{id}/edit", repo.LicensesEditKey) - m.Post("/keys/{id}/edit", repo.LicensesEditKeyPost) - m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey) - }, optSignIn, context.RepoAssignment) + m.Group("", func() { + m.Post("/packages", repo.LicensesCreatePackage) + m.Get("/packages/{id}/edit", repo.LicensesEditPackage) + m.Post("/packages/{id}/edit", repo.LicensesEditPackagePost) + m.Post("/packages/{id}/delete", repo.LicensesDeletePackage) + m.Post("/keys/generate", repo.LicensesGenerateKey) + m.Get("/keys/{id}/edit", repo.LicensesEditKey) + m.Post("/keys/{id}/edit", repo.LicensesEditKeyPost) + m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey) + m.Post("/keys/{id}/renew", repo.LicensesRenewKey) + }, context.RequireUnitWriter(unit.TypeLicenses)) + }, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeLicenses)) // end "/{username}/{reponame}": licenses m.Group("/{username}/{reponame}", func() { // to maintain compatibility with old attachments diff --git a/templates/org/licenses.tmpl b/templates/org/licenses.tmpl index cb709441f1..4f19318e7f 100644 --- a/templates/org/licenses.tmpl +++ b/templates/org/licenses.tmpl @@ -7,7 +7,10 @@
{{ctx.Locale.Tr "repo.licenses.master_key_created"}}

{{ctx.Locale.Tr "repo.licenses.master_key_created_copy"}}

- {{.NewMasterKey}} +
+ + +
{{end}} @@ -15,7 +18,10 @@
{{ctx.Locale.Tr "repo.licenses.key_created"}}

{{ctx.Locale.Tr "repo.licenses.key_created_copy"}}

- {{.NewKeyCreated}} +
+ + +
{{end}} @@ -48,7 +54,7 @@
-

0 = unlimited

+

0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}

@@ -69,7 +75,7 @@ {{end}} {{if .LicensePackages}} - +
@@ -83,16 +89,19 @@ {{range .LicensePackages}} - + {{if $.IsRepoAdmin}} @@ -130,7 +136,7 @@ {{svg "octicon-lock" 16}} {{ctx.Locale.Tr "repo.licenses.issued_keys"}}
-
{{ctx.Locale.Tr "repo.licenses.package_name"}}
{{.Name}}{{if .Description}}
{{.Description}}{{end}}
{{.Name}}{{if eq .Name "Master (Internal)"}} {{ctx.Locale.Tr "repo.licenses.master_label"}}{{end}}{{if .Description}}
{{.Description}}{{end}}
{{if eq .DurationDays 0}}{{ctx.Locale.Tr "repo.licenses.lifetime"}}{{else}}{{.DurationDays}} {{ctx.Locale.Tr "repo.licenses.days"}}{{end}} {{if .AllowedChannels}}{{.AllowedChannels}}{{else}}{{ctx.Locale.Tr "repo.licenses.all_channels"}}{{end}} {{.KeyCount}} {{if .IsActive}}{{ctx.Locale.Tr "repo.licenses.active"}}{{else}}{{ctx.Locale.Tr "repo.licenses.inactive"}}{{end}} -
+ {{$.CsrfTokenHtml}} + {{if or $.IsSiteAdmin $.IsOrganizationOwner}} + + {{end}} @@ -102,12 +111,9 @@ {{svg "octicon-pencil" 14}} {{if $.IsSiteAdmin}} - - {{$.CsrfTokenHtml}} - -
+ {{end}} {{end}}
+
@@ -144,7 +150,7 @@ {{range .LicenseKeys}} - + @@ -155,13 +161,13 @@ {{svg "octicon-pencil" 14}} + {{end}} - - {{$.CsrfTokenHtml}} - - + {{end}} diff --git a/templates/org/licenses_edit_package.tmpl b/templates/org/licenses_edit_package.tmpl index 9074f6d283..fe0ea15e02 100644 --- a/templates/org/licenses_edit_package.tmpl +++ b/templates/org/licenses_edit_package.tmpl @@ -27,7 +27,7 @@
-

0 = unlimited

+

0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}

@@ -47,6 +47,7 @@
+

{{ctx.Locale.Tr "repo.licenses.active_help_package"}}

diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl index 36e3deac0f..1199957581 100644 --- a/templates/org/menu.tmpl +++ b/templates/org/menu.tmpl @@ -28,6 +28,9 @@ {{if and .IsOrganizationMember (or .OrgLicensingEnabled .IsLicensesPage)}} {{svg "octicon-key"}} {{ctx.Locale.Tr "repo.licenses"}} + {{if .NumOrgLicensePackages}} +
{{.NumOrgLicensePackages}}
+ {{end}}
{{end}} {{if and .IsRepoIndexerEnabled .CanReadCode}} diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index 14d3bec42b..62464f51fa 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -26,7 +26,7 @@ {{end}} - {{ctx.Locale.Tr "org.settings.update_streams"}} + {{svg "octicon-key"}} {{ctx.Locale.Tr "org.settings.update_streams"}} {{if .EnableActions}}
diff --git a/templates/repo/licenses.tmpl b/templates/repo/licenses.tmpl index 631d4d536a..e03d5dad11 100644 --- a/templates/repo/licenses.tmpl +++ b/templates/repo/licenses.tmpl @@ -7,7 +7,10 @@
{{ctx.Locale.Tr "repo.licenses.master_key_created"}}

{{ctx.Locale.Tr "repo.licenses.master_key_created_copy"}}

- {{.NewMasterKey}} +
+ + +
{{end}} @@ -15,7 +18,10 @@
{{ctx.Locale.Tr "repo.licenses.key_created"}}

{{ctx.Locale.Tr "repo.licenses.key_created_copy"}}

- {{.NewKeyCreated}} +
+ + +
{{end}} @@ -25,7 +31,7 @@
{{if .LicensePackages}} -
{{ctx.Locale.Tr "repo.licenses.key_prefix"}}
{{.KeyPrefix}}{{if .IsInternal}} Master{{end}}{{.KeyPrefix}}{{if .IsInternal}} {{ctx.Locale.Tr "repo.licenses.master_label"}}{{end}} {{.LicenseeName}}{{if .LicenseeEmail}} ({{.LicenseeEmail}}){{end}} {{if eq .ExpiresUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .ExpiresUnix}}{{end}} {{if eq .LastHeartbeatUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .LastHeartbeatUnix}}{{end}}
+
@@ -39,16 +45,19 @@ {{range .LicensePackages}} - + {{if $.IsRepoAdmin}} @@ -108,7 +114,7 @@
-

0 = unlimited

+

0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}

@@ -136,7 +142,7 @@ {{svg "octicon-lock" 16}} {{ctx.Locale.Tr "repo.licenses.issued_keys"}}
-
{{ctx.Locale.Tr "repo.licenses.package_name"}}
{{.Name}}{{if .Description}}
{{.Description}}{{end}}
{{.Name}}{{if eq .Name "Master (Internal)"}} {{ctx.Locale.Tr "repo.licenses.master_label"}}{{end}}{{if .Description}}
{{.Description}}{{end}}
{{if eq .DurationDays 0}}{{ctx.Locale.Tr "repo.licenses.lifetime"}}{{else}}{{.DurationDays}} {{ctx.Locale.Tr "repo.licenses.days"}}{{end}} {{if .AllowedChannels}}{{.AllowedChannels}}{{else}}{{ctx.Locale.Tr "repo.licenses.all_channels"}}{{end}} {{.KeyCount}} {{if .IsActive}}{{ctx.Locale.Tr "repo.licenses.active"}}{{else}}{{ctx.Locale.Tr "repo.licenses.inactive"}}{{end}} -
+ {{$.CsrfTokenHtml}} + {{if $.IsSiteAdmin}} + + {{end}} @@ -58,12 +67,9 @@ {{svg "octicon-pencil" 14}} {{if $.IsSiteAdmin}} - - {{$.CsrfTokenHtml}} - -
+ {{end}} {{end}}
+
@@ -150,7 +156,7 @@ {{range .LicenseKeys}} - + @@ -161,13 +167,13 @@ {{svg "octicon-pencil" 14}} + {{end}} - - {{$.CsrfTokenHtml}} - - + {{end}} @@ -183,17 +189,17 @@
- +
- - + +
- +
- - + +
diff --git a/templates/repo/licenses_edit_key.tmpl b/templates/repo/licenses_edit_key.tmpl index 10c6706a2c..a340fb1006 100644 --- a/templates/repo/licenses_edit_key.tmpl +++ b/templates/repo/licenses_edit_key.tmpl @@ -43,6 +43,7 @@ +

{{ctx.Locale.Tr "repo.licenses.active_help_key"}}

diff --git a/templates/repo/licenses_edit_package.tmpl b/templates/repo/licenses_edit_package.tmpl index f6843e5a09..85fa31d536 100644 --- a/templates/repo/licenses_edit_package.tmpl +++ b/templates/repo/licenses_edit_package.tmpl @@ -27,7 +27,7 @@
-

0 = unlimited

+

0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}

@@ -47,6 +47,7 @@
+

{{ctx.Locale.Tr "repo.licenses.active_help_package"}}

diff --git a/templates/repo/release_tag_header.tmpl b/templates/repo/release_tag_header.tmpl index 3e83f132d5..2ac848884b 100644 --- a/templates/repo/release_tag_header.tmpl +++ b/templates/repo/release_tag_header.tmpl @@ -16,15 +16,15 @@ {{svg "octicon-rss" 16}} {{ctx.Locale.Tr "rss_feed"}} {{end}} - {{if not .PageIsTagList}} + {{if and (not .PageIsTagList) .LicensingEnabled}} {{if or (eq .RepoUpdatePlatform "joomla") (eq .RepoUpdatePlatform "both") (eq .RepoUpdatePlatform "")}} - {{svg "octicon-download" 16}} Joomla XML + {{svg "octicon-download" 16}} {{ctx.Locale.Tr "repo.licenses.feed_joomla_xml"}} {{end}} {{if or (eq .RepoUpdatePlatform "dolibarr") (eq .RepoUpdatePlatform "both")}} - {{svg "octicon-download" 16}} Dolibarr JSON + {{svg "octicon-download" 16}} {{ctx.Locale.Tr "repo.licenses.feed_dolibarr_json"}} {{end}} {{end}}
{{ctx.Locale.Tr "repo.licenses.key_prefix"}}
{{.KeyPrefix}}{{if .IsInternal}} Master{{end}}{{.KeyPrefix}}{{if .IsInternal}} {{ctx.Locale.Tr "repo.licenses.master_label"}}{{end}} {{.LicenseeName}}{{if .LicenseeEmail}} ({{.LicenseeEmail}}){{end}} {{if eq .ExpiresUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .ExpiresUnix}}{{end}} {{if eq .LastHeartbeatUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .LastHeartbeatUnix}}{{end}}