mirror of https://github.com/Budibase/budibase.git
227 changed files with 6636 additions and 8371 deletions
@ -0,0 +1,11 @@ |
|||||
|
class BudibaseError extends Error { |
||||
|
constructor(message, type, code) { |
||||
|
super(message) |
||||
|
this.type = type |
||||
|
this.code = code |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
BudibaseError, |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
const licensing = require("./licensing") |
||||
|
|
||||
|
const codes = { |
||||
|
...licensing.codes, |
||||
|
} |
||||
|
|
||||
|
const types = { |
||||
|
...licensing.types, |
||||
|
} |
||||
|
|
||||
|
const context = { |
||||
|
...licensing.context, |
||||
|
} |
||||
|
|
||||
|
const getPublicError = err => { |
||||
|
let error |
||||
|
if (err.code || err.type) { |
||||
|
// add generic error information
|
||||
|
error = { |
||||
|
code: err.code, |
||||
|
type: err.type, |
||||
|
} |
||||
|
|
||||
|
if (err.code && context[err.code]) { |
||||
|
error = { |
||||
|
...error, |
||||
|
// get any additional context from this error
|
||||
|
...context[err.code](err), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return error |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
codes, |
||||
|
types, |
||||
|
UsageLimitError: licensing.UsageLimitError, |
||||
|
getPublicError, |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
const { BudibaseError } = require("./base") |
||||
|
|
||||
|
const types = { |
||||
|
LICENSE_ERROR: "license_error", |
||||
|
} |
||||
|
|
||||
|
const codes = { |
||||
|
USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded", |
||||
|
} |
||||
|
|
||||
|
const context = { |
||||
|
[codes.USAGE_LIMIT_EXCEEDED]: err => { |
||||
|
return { |
||||
|
limitName: err.limitName, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class UsageLimitError extends BudibaseError { |
||||
|
constructor(message, limitName) { |
||||
|
super(message, types.LICENSE_ERROR, codes.USAGE_LIMIT_EXCEEDED) |
||||
|
this.limitName = limitName |
||||
|
this.status = 400 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
types, |
||||
|
codes, |
||||
|
context, |
||||
|
UsageLimitError, |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
const env = require("../environment") |
||||
|
const tenancy = require("../tenancy") |
||||
|
|
||||
|
/** |
||||
|
* Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant. |
||||
|
* The env var is formatted as: |
||||
|
* tenant1:feature1:feature2,tenant2:feature1 |
||||
|
*/ |
||||
|
const getFeatureFlags = () => { |
||||
|
if (!env.TENANT_FEATURE_FLAGS) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const tenantFeatureFlags = {} |
||||
|
|
||||
|
env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => { |
||||
|
const [tenantId, ...features] = tenantToFeatures.split(":") |
||||
|
|
||||
|
features.forEach(feature => { |
||||
|
if (!tenantFeatureFlags[tenantId]) { |
||||
|
tenantFeatureFlags[tenantId] = [] |
||||
|
} |
||||
|
tenantFeatureFlags[tenantId].push(feature) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
return tenantFeatureFlags |
||||
|
} |
||||
|
|
||||
|
const TENANT_FEATURE_FLAGS = getFeatureFlags() |
||||
|
|
||||
|
exports.isEnabled = featureFlag => { |
||||
|
const tenantId = tenancy.getTenantId() |
||||
|
|
||||
|
return ( |
||||
|
TENANT_FEATURE_FLAGS && |
||||
|
TENANT_FEATURE_FLAGS[tenantId] && |
||||
|
TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
exports.getTenantFeatureFlags = tenantId => { |
||||
|
if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) { |
||||
|
return TENANT_FEATURE_FLAGS[tenantId] |
||||
|
} |
||||
|
|
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
exports.FeatureFlag = { |
||||
|
LICENSING: "LICENSING", |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,78 @@ |
|||||
|
<script> |
||||
|
import { Select, Label, Combobox } from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" |
||||
|
import { currentAsset, store } from "builderStore" |
||||
|
import { |
||||
|
getActionProviderComponents, |
||||
|
buildFormSchema, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import { findComponent } from "builderStore/componentUtils" |
||||
|
|
||||
|
export let parameters |
||||
|
export let bindings = [] |
||||
|
|
||||
|
const typeOptions = [ |
||||
|
{ |
||||
|
label: "Set value", |
||||
|
value: "set", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Reset to default value", |
||||
|
value: "reset", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
$: formComponent = findComponent($currentAsset.props, parameters.componentId) |
||||
|
$: formSchema = buildFormSchema(formComponent) |
||||
|
$: fieldOptions = Object.keys(formSchema || {}) |
||||
|
$: actionProviders = getActionProviderComponents( |
||||
|
$currentAsset, |
||||
|
$store.selectedComponentId, |
||||
|
"ValidateForm" |
||||
|
) |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!parameters.type) { |
||||
|
parameters.type = "set" |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Label small>Form</Label> |
||||
|
<Select |
||||
|
bind:value={parameters.componentId} |
||||
|
options={actionProviders} |
||||
|
getOptionLabel={x => x._instanceName} |
||||
|
getOptionValue={x => x._id} |
||||
|
/> |
||||
|
<Label small>Type</Label> |
||||
|
<Select |
||||
|
placeholder={null} |
||||
|
bind:value={parameters.type} |
||||
|
options={typeOptions} |
||||
|
/> |
||||
|
<Label small>Field</Label> |
||||
|
<Combobox bind:value={parameters.field} options={fieldOptions} /> |
||||
|
{#if parameters.type === "set"} |
||||
|
<Label small>Value</Label> |
||||
|
<DrawerBindableInput |
||||
|
{bindings} |
||||
|
value={parameters.value} |
||||
|
on:change={e => (parameters.value = e.detail)} |
||||
|
/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
grid-template-columns: 60px 1fr; |
||||
|
align-items: center; |
||||
|
max-width: 400px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,14 @@ |
|||||
|
import { auth } from "../stores/portal" |
||||
|
import { get } from "svelte/store" |
||||
|
|
||||
|
export const FEATURE_FLAGS = { |
||||
|
LICENSING: "LICENSING", |
||||
|
} |
||||
|
|
||||
|
export const isEnabled = featureFlag => { |
||||
|
const user = get(auth).user |
||||
|
if (user?.featureFlags?.includes(featureFlag)) { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
@ -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> |
||||
File diff suppressed because it is too large
@ -1,14 +0,0 @@ |
|||||
{ |
|
||||
// Use IntelliSense to learn about possible attributes. |
|
||||
// Hover to view descriptions of existing attributes. |
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
|
||||
"version": "0.2.0", |
|
||||
"configurations": [ |
|
||||
{ |
|
||||
"type": "node", |
|
||||
"request": "launch", |
|
||||
"name": "Publish Dev", |
|
||||
"program": "${workspaceFolder}/scripts/publishDev.js" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue