diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index f0ee87bde5..a37625a5dc 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -10,6 +10,8 @@ Label, ButtonGroup, notifications, + CopyInput, + File } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -21,44 +23,122 @@ $: license = $auth.user.license $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + // LICENSE KEY $: activateDisabled = !licenseKey || licenseKeyDisabled - - let licenseInfo - let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal + // OFFLINE LICENSE + let installationIdentifier = undefined + let offlineLicense = undefined + const offlineLicenseExtensions = [ + ".txt", + ] + // Make sure page can't be visited directly in cloud $: { if ($admin.cloud) { $redirect("../../portal") } + + console.log({ offlineLicense }) } - const activate = async () => { + // LICENSE KEY + + const getLicenseKey = async () => { try { - await API.activateLicenseKey({ licenseKey }) - await auth.getSelf() - await setLicenseInfo() - notifications.success("Successfully activated") + licenseKey = await API.getLicenseKey() + if (licenseKey) { + licenseKey = "**********************************************" + licenseKeyType = "password" + licenseKeyDisabled = true + activateDisabled = true + } } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error retrieving license key") } } - const destroy = async () => { + const activateLicenseKey = async () => { + try { + await API.activateLicenseKey({ licenseKey }) + await auth.getSelf() + await getLicenseKey() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating license key") + } + } + + const deleteLicenseKey = async () => { try { await API.deleteLicenseKey({ licenseKey }) await auth.getSelf() - await setLicenseInfo() + await getLicenseKey() // reset the form licenseKey = "" licenseKeyDisabled = false - notifications.success("Successfully deleted") + notifications.success("Offline license removed") } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error deleting license key") + } + } + + // OFFLINE LICENSE + + const getOfflineLicense = async () => { + try { + const license = await API.getOfflineLicense() + if (license) { + offlineLicense = { + name: "license" + } + } else { + offlineLicense = undefined + } + } catch (e) { + console.error(e) + notifications.error("Error loading offline license") + } + } + + async function activateOfflineLicense(offlineLicense) { + try { + await API.activateOfflineLicense({ offlineLicense }) + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating offline license") + } + } + + async function deleteOfflineLicense() { + try { + await API.deleteOfflineLicense() + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully removed ofline license") + } catch (e) { + console.error(e) + notifications.error("Error upload offline license") + } + } + + async function onOfflineLicenseChange(event) { + if (event.detail) { + const reader = new FileReader() + reader.readAsText(event.detail) + reader.onload = () => activateOfflineLicense(reader.result) + } else { + await deleteOfflineLicense() } } @@ -73,29 +153,19 @@ } } - // 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() + if ($admin.offlineMode) { + await getOfflineLicense() + } else { + await getLicenseKey() + } }) {#if $auth.isAdmin} @@ -112,38 +182,73 @@ - - Activate - Enter your license key below to activate your plan - - -
-
- - + Installation identifier + Share this with support@budibase.com to obtain your offline license + + +
+
+ +
+
+
+ + + License + Upload your license to activate your plan + + +
+
-
- - - {#if licenseInfo?.licenseKey} - - {/if} - - + {#if licenseKey} + + {/if} + + + {/if} - Plan - + Plan + You are currently on the {license.plan.type} plan +
+ If you purchase or update your plan on the account + portal, click the refresh button to sync those changes +
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index b9467fd037..19abcfd841 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, + offlineMode: false } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index c27d79d740..5f7813cfca 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,32 +1,56 @@ export const buildLicensingEndpoints = API => ({ - /** - * Activates a self hosted license key - */ + + // LICENSE KEY + activateLicenseKey: async data => { return API.post({ - url: `/api/global/license/activate`, + url: `/api/global/license/key`, body: data, }) }, - - /** - * Delete a self hosted license key - */ deleteLicenseKey: async () => { return API.delete({ - url: `/api/global/license/info`, + url: `/api/global/license/key`, }) }, + getLicenseKey: async () => { + try { + return await API.get({ + url: "/api/global/license/key", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, - /** - * Get the license info - metadata about the license including the - * obfuscated license key. - */ - getLicenseInfo: async () => { - return API.get({ - url: "/api/global/license/info", + // OFFLINE LICENSE + + activateOfflineLicense: async ({ offlineLicense }) => { + return API.post({ + url: "/api/global/license/offline", + body: { + offlineLicense + }, }) }, + deleteOfflineLicense: async () => { + return API.delete({ + url: "/api/global/license/offline", + }) + }, + getOfflineLicense: async () => { + try { + return await API.get({ + url: "/api/global/license/offline", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, /** * Refreshes the license cache @@ -36,7 +60,6 @@ export const buildLicensingEndpoints = API => ({ url: "/api/global/license/refresh", }) }, - /** * Retrieve the usage information for the tenant */