Files
dc/client/src/app/licensing/licensing.component.html
T
YuryShkoda 20ba633087 feat(licensing): support combined-key paste, object-format features, and a live key preview
- Accept a single pasted/uploaded combined licence key (base64+gzip of
  licence+activation key), auto-detected in either paste mode, alongside
  the legacy two-field format
- Warn and block applying a key generated for the wrong protocol before
  it ever reaches the backend
- Decode both the legacy positional features string and a new named
  features object, so already-issued keys keep working unchanged
- Show a live "Key Details" preview (validity, users, site IDs, enabled
  features) as a key is typed, pasted, or uploaded
- Fix cypress test setup broken by the paste-format default change, and
  an unreliable FileReader-based base64 conversion in the test helpers
2026-08-28 20:01:45 +01:00

262 lines
8.2 KiB
HTML

<div *ngIf="action !== 'register'" class="card">
<div class="card-header">Licencing</div>
<div [ngSwitch]="action" class="card-block">
<div class="card-text">
<ng-container *ngSwitchCase="'key'">
<p class="key-error" *ngIf="!keyError">
<clr-icon
class="is-error"
shape="exclamation-circle"
size="26"
></clr-icon>
Licence key is invalid. We can't provide you more details at the
moment
</p>
<p
class="key-error"
*ngIf="keyError"
[innerHTML]="licenseErrors[keyError]"
></p>
<p *ngIf="errorDetails"><strong>Details:</strong> {{ errorDetails }}</p>
</ng-container>
<ng-container *ngSwitchCase="'limit'">
<p class="key-error">
<clr-icon
class="is-error"
shape="exclamation-circle"
size="26"
></clr-icon>
The registered number of users reached the limit specified for your
licence. Please contact
<contact-link classes="color-green" />
or your reseller to arrange additional licences for this product.
</p>
</ng-container>
<ng-container *ngSwitchCase="'update'">
<p class="key-error">
Update the license key by uploading the licence file or by pasting a
license key and activation key in the inputs below.
</p>
</ng-container>
<p class="m-0 mt-10"><strong>Protocol:</strong> {{ protocol }}</p>
<p class="m-0">
<strong>SYSSITE:</strong>
<span
*ngFor="let id of syssite.value; let i = index"
[class.misskey]="missmatchedKey && missmatchedKey === id"
>
{{ id }}{{ i === syssite.value?.length! - 1 ? '' : ',' }}
</span>
<a
class="tooltip tooltip-md tooltip-top-right"
(click)="copySyssite(copyIcon, copyTooltip, syssite.value || [])"
>
<clr-icon
#copyIcon
class="cursor-pointer"
shape="copy"
size="15"
></clr-icon>
<span #copyTooltip class="tooltip-content">Copy to clipboard</span>
</a>
</p>
<clr-tabs>
<clr-tab>
<button clrTabLink>Upload licence</button>
<clr-tab-content>
<input
#licenceFile
(change)="onFileCapture($event)"
type="file"
hidden
/>
<div
(click)="licenceFile.click()"
appFileDrop
(fileDrop)="onFileCapture($event, true)"
class="drop-area"
>
<clr-spinner
class="spinner-sm"
*ngIf="licenceFileLoading"
></clr-spinner>
<ng-container *ngIf="!licenceFileLoading">
<div *ngIf="licencefile.filename === ''">
Drop / Browse licence file
</div>
<div *ngIf="licencefile.filename !== ''">
Selected file: <strong>{{ licencefile.filename }}</strong>
</div>
<div *ngIf="licenceFileError">
<strong>{{ licenceFileError }}</strong>
</div>
</ng-container>
</div>
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink>Paste licence</button>
<clr-tab-content>
<ng-container *ngIf="keyFormat === 'combined'">
<form class="clr-form combined-key-form">
<p>Licence key:</p>
<div class="clr-control-container">
<textarea
[(ngModel)]="combinedKeyValue"
(mouseleave)="onCombinedKeyInput()"
name="combined-key-area"
placeholder="Paste licence key here"
class="clr-textarea"
></textarea>
</div>
</form>
<button
type="button"
class="btn btn-sm btn-link p-0"
(click)="toggleKeyFormat()"
>
Paste as two separate keys instead
</button>
</ng-container>
<ng-container *ngIf="keyFormat === 'legacy'">
<form class="clr-form license-key-form">
<p>Licence key:</p>
<div class="clr-control-container">
<textarea
[(ngModel)]="licenceKeyValue"
(mouseleave)="trimKeys()"
name="license-key-area"
placeholder="Paste licence key here"
class="clr-textarea"
></textarea>
</div>
</form>
<form class="clr-form activation-key-form">
<p>Activation key:</p>
<div class="clr-control-container">
<textarea
[(ngModel)]="activationKeyValue"
(mouseleave)="trimKeys()"
name="activation-key-area"
placeholder="Paste activation key here"
class="clr-textarea"
></textarea>
</div>
</form>
<button
type="button"
class="btn btn-sm btn-link p-0"
(click)="toggleKeyFormat()"
>
Paste as a single key instead
</button>
</ng-container>
</clr-tab-content>
</clr-tab>
</clr-tabs>
<!--
licenseKeyData starts as whatever key is currently active (set once
in ngOnInit), then live-updates to preview a pasted-but-not-yet-
applied key's own details instead - see refreshKeyPreview().
-->
<div *ngIf="licenseKeyData" class="key-details">
<h6 cds-text="subsection" class="mt-15 mb-10">Key Details</h6>
<p class="m-0">
<strong>Valid until:</strong>
{{ licenseKeyData.valid_until }}
<span *ngIf="licenseKeyData.demo">(demo/free tier key)</span>
</p>
<p class="m-0">
<strong>Allowed users:</strong>
{{ licenseKeyData.users_allowed }}
</p>
<p class="m-0">
<strong>Site ID(s) in this key:</strong>
{{
(licenseKeyData.site_id_multiple?.length
? licenseKeyData.site_id_multiple
: [licenseKeyData.site_id]
).join(', ')
}}
</p>
<p *ngIf="enabledFeatures.length" class="m-0">
<strong>Enabled features:</strong>
{{ enabledFeatures.join(', ') }}
</p>
</div>
</div>
<div class="card-footer d-flex clr-align-items-center">
<button
(click)="applyKeys()"
class="btn btn-primary apply-keys"
[clrLoading]="applyingKeys"
[disabled]="disableApplyButton"
>
Apply licence keys
</button>
<button
*ngIf="isAppFreeTier.value"
routerLink="/"
class="btn btn-sm btn-link"
>
Continue with free tier
</button>
</div>
<p
class="key-error protocol-mismatch-warning"
*ngIf="protocolMismatch === 'requiresHttps'"
>
<clr-icon
class="color-orange"
shape="exclamation-triangle"
size="26"
></clr-icon>
This key was generated for a secure (HTTPS) connection, but DataController
is currently running over HTTP - it will not activate here. Access
DataController via HTTPS, or contact
<contact-link classes="color-green" />
for an HTTP-compatible key.
</p>
<p
class="key-error protocol-mismatch-warning"
*ngIf="protocolMismatch === 'requiresHttp'"
>
<clr-icon
class="color-orange"
shape="exclamation-triangle"
size="26"
></clr-icon>
This key was generated for an insecure (HTTP) connection, but this page is
running in a secure browsing context (HTTPS, or localhost) - it will not
activate here. Contact
<contact-link classes="color-green" />
for an HTTPS-compatible key.
</p>
</div>
</div>
<app-terms *ngIf="action === 'register'"></app-terms>