mirror of https://github.com/Budibase/budibase.git
26 changed files with 1605 additions and 191 deletions
@ -0,0 +1,151 @@ |
|||
<script> |
|||
import { |
|||
Layout, |
|||
Heading, |
|||
Body, |
|||
Divider, |
|||
Link, |
|||
Button, |
|||
Input, |
|||
Label, |
|||
notifications, |
|||
} from "@budibase/bbui" |
|||
import { auth, admin } from "stores/portal" |
|||
import { redirect } from "@roxi/routify" |
|||
import { processStringSync } from "@budibase/string-templates" |
|||
import { API } from "api" |
|||
import { onMount } from "svelte" |
|||
|
|||
$: license = $auth.user.license |
|||
$: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` |
|||
|
|||
$: activateDisabled = !licenseKey || licenseKeyDisabled |
|||
|
|||
let licenseInfo |
|||
|
|||
let licenseKeyDisabled = false |
|||
let licenseKeyType = "text" |
|||
let licenseKey = "" |
|||
|
|||
// Make sure page can't be visited directly in cloud |
|||
$: { |
|||
if ($admin.cloud) { |
|||
$redirect("../../portal") |
|||
} |
|||
} |
|||
|
|||
const activate = async () => { |
|||
await API.activateLicenseKey({ licenseKey }) |
|||
await auth.getSelf() |
|||
await setLicenseInfo() |
|||
notifications.success("Successfully activated") |
|||
} |
|||
|
|||
const refresh = async () => { |
|||
try { |
|||
await API.refreshLicense() |
|||
await auth.getSelf() |
|||
notifications.success("Refreshed license") |
|||
} catch (err) { |
|||
console.error(err) |
|||
notifications.error("Error refreshing license") |
|||
} |
|||
} |
|||
|
|||
// deactivate the license key field if there is a license key set |
|||
$: { |
|||
if (licenseInfo?.licenseKey) { |
|||
licenseKey = "**********************************************" |
|||
licenseKeyType = "password" |
|||
licenseKeyDisabled = true |
|||
activateDisabled = true |
|||
} |
|||
} |
|||
|
|||
const setLicenseInfo = async () => { |
|||
licenseInfo = await API.getLicenseInfo() |
|||
} |
|||
|
|||
onMount(async () => { |
|||
await setLicenseInfo() |
|||
}) |
|||
</script> |
|||
|
|||
{#if $auth.isAdmin} |
|||
<Layout noPadding> |
|||
<Layout gap="XS" noPadding> |
|||
<Heading size="M">Upgrade</Heading> |
|||
<Body size="M"> |
|||
{#if license.plan.type === "free"} |
|||
Upgrade your budibase installation to unlock additional features. To |
|||
subscribe to a plan visit your <Link size="L" href={upgradeUrl} |
|||
>Account</Link |
|||
>. |
|||
{:else} |
|||
To manage your plan visit your <Link size="L" href={upgradeUrl} |
|||
>Account</Link |
|||
>. |
|||
{/if} |
|||
</Body> |
|||
</Layout> |
|||
<Divider size="S" /> |
|||
<Layout gap="XS" noPadding> |
|||
<Heading size="S">Activate</Heading> |
|||
<Body size="S">Enter your license key below to activate your plan</Body> |
|||
</Layout> |
|||
<Layout noPadding> |
|||
<div class="fields"> |
|||
<div class="field"> |
|||
<Label size="L">License Key</Label> |
|||
<Input |
|||
thin |
|||
bind:value={licenseKey} |
|||
type={licenseKeyType} |
|||
disabled={licenseKeyDisabled} |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div> |
|||
<Button cta on:click={activate} disabled={activateDisabled} |
|||
>Activate</Button |
|||
> |
|||
</div> |
|||
</Layout> |
|||
<Divider size="S" /> |
|||
<Layout gap="L" noPadding> |
|||
<Layout gap="S" noPadding> |
|||
<Heading size="S">Plan</Heading> |
|||
<Layout noPadding gap="XXS"> |
|||
<Body size="S">You are currently on the {license.plan.type} plan</Body |
|||
> |
|||
<Body size="XS"> |
|||
{processStringSync( |
|||
"Updated {{ duration time 'millisecond' }} ago", |
|||
{ |
|||
time: |
|||
new Date().getTime() - |
|||
new Date(license.refreshedAt).getTime(), |
|||
} |
|||
)} |
|||
</Body> |
|||
</Layout> |
|||
</Layout> |
|||
<div> |
|||
<Button secondary on:click={refresh}>Refresh</Button> |
|||
</div> |
|||
</Layout> |
|||
</Layout> |
|||
{/if} |
|||
|
|||
<style> |
|||
.fields { |
|||
display: grid; |
|||
grid-gap: var(--spacing-m); |
|||
} |
|||
.field { |
|||
display: grid; |
|||
grid-template-columns: 100px 1fr; |
|||
grid-gap: var(--spacing-l); |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,30 @@ |
|||
export const buildLicensingEndpoints = API => ({ |
|||
/** |
|||
* Activates a self hosted license key |
|||
*/ |
|||
activateLicenseKey: async data => { |
|||
return API.post({ |
|||
url: `/api/global/license/activate`, |
|||
body: data, |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* Get the license info - metadata about the license including the |
|||
* obfuscated license key. |
|||
*/ |
|||
getLicenseInfo: async () => { |
|||
return API.get({ |
|||
url: "/api/global/license/info", |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* Refreshes the license cache |
|||
*/ |
|||
refreshLicense: async () => { |
|||
return API.post({ |
|||
url: "/api/global/license/refresh", |
|||
}) |
|||
}, |
|||
}) |
|||
@ -0,0 +1,25 @@ |
|||
import * as Pro from "@budibase/pro" |
|||
|
|||
export const activate = async (ctx: any) => { |
|||
const { licenseKey } = ctx.request.body |
|||
if (!licenseKey) { |
|||
ctx.throw(400, "licenseKey is required") |
|||
} |
|||
|
|||
await Pro.Licensing.activateLicenseKey(licenseKey) |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
export const refresh = async (ctx: any) => { |
|||
await Pro.Licensing.Cache.refresh() |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
export const getInfo = async (ctx: any) => { |
|||
const licenseInfo = await Pro.Licensing.getLicenseInfo() |
|||
if (licenseInfo) { |
|||
licenseInfo.licenseKey = "*" |
|||
ctx.body = licenseInfo |
|||
} |
|||
ctx.status = 200 |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
import Router from "@koa/router" |
|||
import * as controller from "../../controllers/global/license" |
|||
|
|||
const router = new Router() |
|||
|
|||
router |
|||
.post("/api/global/license/activate", controller.activate) |
|||
.post("/api/global/license/refresh", controller.refresh) |
|||
.get("/api/global/license/info", controller.getInfo) |
|||
|
|||
export = router |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue