Merge pull request 'fix(licenses): fix master key visibility and package creation at repo level' (#461) from fix/licenses-ui into dev
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
This commit was merged in pull request #461.
This commit is contained in:
@@ -2660,6 +2660,9 @@
|
||||
"repo.licenses.key_revoked": "License key revoked.",
|
||||
"repo.licenses.master_key_created": "Master License Key Created",
|
||||
"repo.licenses.master_key_created_copy": "This is your organization master key with unlimited access to all update channels. Copy it now — it will not be shown again.",
|
||||
"repo.licenses.regenerate_master_key": "Regenerate",
|
||||
"repo.licenses.regenerate_master_key_help": "Deactivates the current master key and generates a new one. The new key will be shown once.",
|
||||
"repo.licenses.master_key_regenerated": "Master key regenerated. Copy the new key below — it will not be shown again.",
|
||||
"repo.licenses.update_feeds": "Update Feed URLs",
|
||||
"repo.licenses.edit_key": "Edit License Key",
|
||||
"repo.licenses.licensee_name": "Licensee Name",
|
||||
|
||||
@@ -90,6 +90,10 @@ func Licenses(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Always load the master key for display (prefix + status).
|
||||
masterKey, _ := licenses.GetMasterKey(ctx, ownerID)
|
||||
ctx.Data["MasterKey"] = masterKey
|
||||
|
||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListLicensePackages", err)
|
||||
@@ -203,6 +207,54 @@ func LicensesCreatePackage(ctx *context.Context) {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
// LicensesRegenerateMasterKey handles POST to regenerate the master key.
|
||||
func LicensesRegenerateMasterKey(ctx *context.Context) {
|
||||
ownerID := ctx.Repo.Repository.OwnerID
|
||||
|
||||
// Deactivate the old master key.
|
||||
oldKey, _ := licenses.GetMasterKey(ctx, ownerID)
|
||||
if oldKey != nil {
|
||||
oldKey.IsActive = false
|
||||
_ = licenses.UpdateLicenseKey(ctx, oldKey)
|
||||
}
|
||||
|
||||
// Find the master package.
|
||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListLicensePackages", err)
|
||||
return
|
||||
}
|
||||
var masterPkg *licenses.LicensePackage
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Name == licenses.MasterPackageName {
|
||||
masterPkg = pkg
|
||||
break
|
||||
}
|
||||
}
|
||||
if masterPkg == nil {
|
||||
ctx.Flash.Error("Master package not found")
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new master key.
|
||||
newKey := &licenses.LicenseKey{
|
||||
PackageID: masterPkg.ID,
|
||||
OwnerID: ownerID,
|
||||
IsInternal: true,
|
||||
IsActive: true,
|
||||
}
|
||||
rawKey, err := licenses.CreateLicenseKey(ctx, newKey)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateLicenseKey", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.master_key_regenerated"))
|
||||
ctx.Data["NewMasterKey"] = rawKey
|
||||
Licenses(ctx)
|
||||
}
|
||||
|
||||
// LicensesGenerateKey handles POST to generate a new key from a package.
|
||||
func LicensesGenerateKey(ctx *context.Context) {
|
||||
packageID, _ := strconv.ParseInt(ctx.FormString("package_id"), 10, 64)
|
||||
|
||||
@@ -1549,6 +1549,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Post("/packages/{id}/archive", repo.LicensesArchivePackage)
|
||||
m.Post("/packages/{id}/unarchive", repo.LicensesUnarchivePackage)
|
||||
m.Post("/keys/generate", repo.LicensesGenerateKey)
|
||||
m.Post("/master-key/regenerate", repo.LicensesRegenerateMasterKey)
|
||||
m.Get("/keys/{id}/edit", repo.LicensesEditKey)
|
||||
m.Post("/keys/{id}/edit", repo.LicensesEditKeyPost)
|
||||
m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey)
|
||||
|
||||
@@ -25,12 +25,32 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ── Master Key Info ── */}}
|
||||
{{if and .MasterKey .IsRepoAdmin}}
|
||||
<div class="ui segment tw-flex tw-items-center tw-justify-between tw-gap-4">
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>
|
||||
<code>{{.MasterKey.KeyPrefix}}</code>
|
||||
{{if .MasterKey.IsActive}}
|
||||
<span class="ui tiny green label">{{ctx.Locale.Tr "repo.licenses.active"}}</span>
|
||||
{{else}}
|
||||
<span class="ui tiny grey label">{{ctx.Locale.Tr "repo.licenses.inactive"}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<form method="post" action="{{.RepoLink}}/licenses/master-key/regenerate" class="tw-inline">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button class="ui tiny primary button" type="submit" data-tooltip-content="{{ctx.Locale.Tr "repo.licenses.regenerate_master_key_help"}}">
|
||||
{{svg "octicon-sync" 14}} {{ctx.Locale.Tr "repo.licenses.regenerate_master_key"}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ── License Packages ── */}}
|
||||
<details id="new-package-details">
|
||||
<h4 class="ui top attached header tw-flex tw-items-center tw-justify-between">
|
||||
<span>{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}</span>
|
||||
{{if .IsRepoAdmin}}
|
||||
<summary class="ui primary small button">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</summary>
|
||||
<button class="ui primary small button show-modal" data-modal="#new-package-modal">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</button>
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
@@ -94,60 +114,6 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{/* ── Create New License Package (form panel, toggled by header button) ── */}}
|
||||
{{if .IsRepoAdmin}}
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" placeholder="e.g. Annual pro subscription with all channels">
|
||||
</div>
|
||||
</div>
|
||||
<div class="fields">
|
||||
<div class="four wide field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}</p>
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.domain_lock_hours"}}</label>
|
||||
<input name="domain_lock_hours" type="number" value="0" min="0">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.domain_lock_hours_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
{{template "shared/combolist" dict "Name" "allowed_channels" "Title" (ctx.Locale.Tr "repo.licenses.channels") "Items" $.ChannelItems "SelectedValues" "" "EmptyText" (ctx.Locale.Tr "repo.licenses.all_channels") "Icon" "octicon-tag"}}
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.repo_scope"}}</label>
|
||||
<select name="repo_scope" class="ui dropdown">
|
||||
<option value="all">{{ctx.Locale.Tr "repo.licenses.repo_scope_all"}}</option>
|
||||
{{if .OrgRepos}}
|
||||
{{range .OrgRepos}}
|
||||
<option value="{{.ID}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.repo_scope_help"}}</p>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
{{end}}
|
||||
|
||||
{{/* ── Issued Keys ── */}}
|
||||
{{if or .LicenseKeys .SearchQuery}}
|
||||
<h4 class="ui top attached header tw-mt-4">
|
||||
@@ -183,7 +149,7 @@
|
||||
<td>
|
||||
<div class="tw-flex tw-items-center tw-gap-1">
|
||||
<code class="js-license-key-{{.ID}}">{{if .KeyRaw}}{{.KeyRaw}}{{else}}{{.KeyPrefix}}{{end}}</code>
|
||||
{{if .KeyRaw}}<button class="ui tiny icon button" data-clipboard-target=".js-license-key-{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 12}}</button>{{end}}
|
||||
<button class="ui tiny icon button" data-clipboard-text="{{if .KeyRaw}}{{.KeyRaw}}{{else}}{{.KeyPrefix}}{{end}}" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 12}}</button>
|
||||
{{if .IsInternal}} <span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>{{end}}
|
||||
</div>
|
||||
</td>
|
||||
@@ -309,6 +275,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* ── Create New License Package Modal ── */}}
|
||||
{{if .IsRepoAdmin}}
|
||||
<div class="ui small modal" id="new-package-modal">
|
||||
<div class="header">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</div>
|
||||
<div class="content">
|
||||
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" placeholder="e.g. Annual pro subscription with all channels">
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.unlimited"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.domain_lock_hours"}}</label>
|
||||
<input name="domain_lock_hours" type="number" value="0" min="0">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.domain_lock_hours_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
{{template "shared/combolist" dict "Name" "allowed_channels" "Title" (ctx.Locale.Tr "repo.licenses.channels") "Items" $.ChannelItems "SelectedValues" "" "EmptyText" (ctx.Locale.Tr "repo.licenses.all_channels") "Icon" "octicon-tag"}}
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.repo_scope"}}</label>
|
||||
<select name="repo_scope" class="ui dropdown">
|
||||
<option value="all">{{ctx.Locale.Tr "repo.licenses.repo_scope_all"}}</option>
|
||||
{{if .OrgRepos}}
|
||||
{{range .OrgRepos}}
|
||||
<option value="{{.ID}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.repo_scope_help"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.licenses.create_package"))}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ── Delete Package Confirmation Modal ── */}}
|
||||
<div class="ui small modal" id="license-delete-package-modal">
|
||||
<div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.licenses.delete_package"}}</div>
|
||||
|
||||
Reference in New Issue
Block a user