Merge pull request #5953 from Budibase/feature/portal-usage

Porting usage UI to builder
This commit is contained in:
Michael Drury 2022-05-19 22:54:34 +01:00 committed by GitHub
commit f7dfda1b41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 318 additions and 66 deletions

View File

@ -13,6 +13,7 @@
"@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.901.0",
"bcryptjs": "^2.4.3",
"dotenv": "^16.0.1",
"emitter-listener": "^1.1.2",
"ioredis": "^4.27.1",
"jsonwebtoken": "^8.5.1",

View File

@ -10,7 +10,15 @@ function isDev() {
return process.env.NODE_ENV !== "production"
}
let LOADED = false
if (!LOADED && isDev() && !isTest()) {
require("dotenv").config()
LOADED = true
}
module.exports = {
isTest,
isDev,
JWT_SECRET: process.env.JWT_SECRET,
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
@ -41,8 +49,7 @@ module.exports = {
GLOBAL_CLOUD_BUCKET_NAME:
process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads",
USE_COUCH: process.env.USE_COUCH || true,
isTest,
isDev,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
_set(key, value) {
process.env[key] = value
module.exports[key] = value

View File

@ -19,5 +19,6 @@ module.exports = {
env: require("./environment"),
accounts: require("./cloud/accounts"),
tenancy: require("./tenancy"),
context: require("../context"),
featureFlags: require("./featureFlags"),
}

View File

@ -1485,6 +1485,11 @@ domexception@^2.0.1:
dependencies:
webidl-conversions "^5.0.0"
dotenv@^16.0.1:
version "16.0.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
double-ended-queue@2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"

View File

@ -7,7 +7,6 @@ const tmpdir = path.join(require("os").tmpdir(), ".budibase")
const SERVER_PORT = cypressConfig.env.PORT
const WORKER_PORT = cypressConfig.env.WORKER_PORT
process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "false"
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET

View File

@ -0,0 +1,56 @@
<script>
import { Body, ProgressBar, Label } from "@budibase/bbui"
import { onMount } from "svelte"
export let usage
let percentage
let unlimited = false
const isUnlimited = () => {
if (usage.total === -1) {
return true
}
return false
}
const getPercentage = () => {
return Math.min(Math.ceil((usage.used / usage.total) * 100), 100)
}
onMount(() => {
unlimited = isUnlimited()
percentage = getPercentage()
})
</script>
<div class="usage">
<div class="info">
<Label size="XL">{usage.name}</Label>
{#if unlimited}
<Body size="S">{usage.used}</Body>
{:else}
<Body size="S">{usage.used} / {usage.total}</Body>
{/if}
</div>
<div>
{#if unlimited}
<Body size="S">Unlimited</Body>
{:else}
<ProgressBar width={"100%"} duration={1} value={percentage} />
{/if}
</div>
</div>
<style>
.usage {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.info {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: var(--spacing-m);
}
</style>

View File

@ -31,7 +31,20 @@
$: menu = buildMenu($auth.isAdmin)
const buildMenu = admin => {
let menu = [{ title: "Apps", href: "/builder/portal/apps" }]
let menu = [
{
title: "Apps",
href: "/builder/portal/apps",
},
]
if (isEnabled(FEATURE_FLAGS.LICENSING)) {
menu = menu.concat([
{
title: "Usage",
href: "/builder/portal/settings/usage",
},
])
}
if (admin) {
menu = menu.concat([
{

View File

@ -12,7 +12,7 @@
</script>
{#if $auth.isAdmin}
<Page wide={$page.path.includes("email/:template")}>
<Page maxWidth="90ch" wide={$page.path.includes("email/:template")}>
<slot />
</Page>
{/if}

View File

@ -297,7 +297,7 @@
</Body>
</Layout>
{#if providers.google}
<Divider />
<Divider size="S" />
<Layout gap="XS" noPadding>
<Heading size="S">
<div class="provider-title">
@ -336,7 +336,7 @@
</Layout>
{/if}
{#if providers.oidc}
<Divider />
<Divider size="S" />
<Layout gap="XS" noPadding>
<Heading size="S">
<div class="provider-title">

View File

@ -132,7 +132,7 @@
values below and click activate.
</Body>
</Layout>
<Divider />
<Divider size="S" />
{#if smtpConfig}
<Layout gap="XS" noPadding>
<Heading size="S">SMTP</Heading>
@ -186,7 +186,7 @@
Reset
</Button>
</div>
<Divider />
<Divider size="S" />
<Layout gap="XS" noPadding>
<Heading size="S">Templates</Heading>
<Body size="S">

View File

@ -2,6 +2,6 @@
import { Page } from "@budibase/bbui"
</script>
<Page>
<Page maxWidth="90ch">
<slot />
</Page>

View File

@ -0,0 +1,139 @@
<script>
import {
Body,
Divider,
Heading,
Layout,
notifications,
Link,
} from "@budibase/bbui"
import { onMount } from "svelte"
import { admin, auth, licensing } from "stores/portal"
import Usage from "components/usage/Usage.svelte"
let staticUsage = []
let monthlyUsage = []
let loaded = false
$: quotaUsage = $licensing.quotaUsage
$: license = $auth.user?.license
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
const setMonthlyUsage = () => {
monthlyUsage = []
if (quotaUsage.monthly) {
for (let [key, value] of Object.entries(license.quotas.usage.monthly)) {
const used = quotaUsage.monthly.current[key]
if (used !== undefined) {
monthlyUsage.push({
name: value.name,
used: used,
total: value.value,
})
}
}
}
}
const setStaticUsage = () => {
staticUsage = []
for (let [key, value] of Object.entries(license.quotas.usage.static)) {
const used = quotaUsage.usageQuota[key]
if (used !== undefined) {
staticUsage.push({
name: value.name,
used: used,
total: value.value,
})
}
}
}
const capitalise = string => {
if (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
}
const init = async () => {
try {
await licensing.getQuotaUsage()
} catch (e) {
console.error(e)
notifications.error(e)
}
}
onMount(async () => {
await init()
loaded = true
})
$: {
if (license && quotaUsage) {
setMonthlyUsage()
setStaticUsage()
}
}
</script>
{#if loaded}
<Layout>
<Heading>Usage</Heading>
<Body
>Get information about your current usage within Budibase.
{#if $admin.cloud}
{#if $auth.user?.accountPortalAccess}
To upgrade your plan and usage limits visit your <Link
size="L"
href={upgradeUrl}>Account</Link
>.
{:else}
Contact your account holder to upgrade your usage limits.
{/if}
{/if}
</Body>
</Layout>
<Layout gap="S">
<Divider size="S" />
</Layout>
<Layout gap="S" noPadding>
<Layout gap="XS">
<Body size="S">YOUR PLAN</Body>
<Heading size="S">{capitalise(license?.plan.type)}</Heading>
</Layout>
<Layout gap="S">
<Body size="S">USAGE</Body>
<div class="usages">
{#each staticUsage as usage}
<div class="usage">
<Usage {usage} />
</div>
{/each}
</div>
</Layout>
{#if monthlyUsage.length}
<Layout gap="S">
<Body size="S">MONTHLY</Body>
<div class="usages">
{#each monthlyUsage as usage}
<div class="usage">
<Usage {usage} />
</div>
{/each}
</div>
</Layout>
<div />
{/if}
</Layout>
{/if}
<style>
.usages {
display: grid;
column-gap: 60px;
row-gap: 50px;
grid-template-columns: 1fr 1fr 1fr;
}
</style>

View File

@ -6,3 +6,4 @@ export { email } from "./email"
export { auth } from "./auth"
export { oidc } from "./oidc"
export { templates } from "./templates"
export { licensing } from "./licensing"

View File

@ -0,0 +1,29 @@
import { writable } from "svelte/store"
import { API } from "api"
export const createLicensingStore = () => {
const DEFAULT = {
plans: {},
}
const store = writable(DEFAULT)
const actions = {
getQuotaUsage: async () => {
const quotaUsage = await API.getQuotaUsage()
store.update(state => {
return {
...state,
quotaUsage,
}
})
},
}
return {
subscribe: store.subscribe,
...actions,
}
}
export const licensing = createLicensingStore()

View File

@ -27,4 +27,13 @@ export const buildLicensingEndpoints = API => ({
url: "/api/global/license/refresh",
})
},
/**
* Retrieve the usage information for the tenant
*/
getQuotaUsage: async () => {
return API.get({
url: "/api/global/license/usage",
})
},
})

View File

@ -9,7 +9,6 @@ const SERVER_PORT = "4100"
const WORKER_PORT = "4200"
// @ts-ignore
process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "false"
process.env.JWT_SECRET = "budibase"

View File

@ -39,15 +39,12 @@ module.exports = {
COUCH_DB_URL: process.env.COUCH_DB_URL,
MINIO_URL: process.env.MINIO_URL,
WORKER_URL: process.env.WORKER_URL,
SELF_HOSTED: process.env.SELF_HOSTED,
AWS_REGION: process.env.AWS_REGION,
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
REDIS_URL: process.env.REDIS_URL,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
MULTI_TENANCY: process.env.MULTI_TENANCY,
HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS,
API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
@ -57,28 +54,26 @@ module.exports = {
JEST_WORKER_ID: process.env.JEST_WORKER_ID,
BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT,
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
TEMPLATE_REPOSITORY: process.env.TEMPLATE_REPOSITORY || "app",
// minor
SALT_ROUNDS: process.env.SALT_ROUNDS,
LOGGER: process.env.LOGGER,
LOG_LEVEL: process.env.LOG_LEVEL,
AUTOMATION_DIRECTORY: process.env.AUTOMATION_DIRECTORY,
AUTOMATION_BUCKET: process.env.AUTOMATION_BUCKET,
AUTOMATION_MAX_ITERATIONS: process.env.AUTOMATION_MAX_ITERATIONS,
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT,
POSTHOG_TOKEN: process.env.POSTHOG_TOKEN,
QUERY_THREAD_TIMEOUT: parseIntSafe(process.env.QUERY_THREAD_TIMEOUT),
// old - to remove
CLIENT_ID: process.env.CLIENT_ID,
BUDIBASE_DIR: process.env.BUDIBASE_DIR,
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY,
USERID_API_KEY: process.env.USERID_API_KEY,
DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL,
SQL_MAX_ROWS: process.env.SQL_MAX_ROWS,
// flags
ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS,
DISABLE_THREADING: process.env.DISABLE_THREADING,
SQL_MAX_ROWS: process.env.SQL_MAX_ROWS,
TEMPLATE_REPOSITORY: process.env.TEMPLATE_REPOSITORY || "app",
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
MULTI_TENANCY: process.env.MULTI_TENANCY,
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
SELF_HOSTED: process.env.SELF_HOSTED,
// old
CLIENT_ID: process.env.CLIENT_ID,
_set(key, value) {
process.env[key] = value
module.exports[key] = value

View File

@ -1,10 +1,3 @@
const { join } = require("./centralPath")
const { homedir } = require("os")
const env = require("../environment")
const { budibaseTempDir } = require("@budibase/backend-core/objectStore")
module.exports.budibaseAppsDir = function () {
return env.BUDIBASE_DIR || join(homedir(), ".budibase")
}
module.exports.budibaseTempDir = budibaseTempDir

View File

@ -1014,10 +1014,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.0.164":
version "1.0.164"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.164.tgz#08c111dcebf5c74159a3c18218c7b3a0716de4f6"
integrity sha512-lpMudezndUD1hHBLfT9LDNKCunj8rQNlaJb30/xggdIUvp718u/jVP54hXF26NYxXOTMZ0EvMwCsIS4AucJ1Mg==
"@budibase/backend-core@1.0.167-alpha.3":
version "1.0.167-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.167-alpha.3.tgz#bb82ba4bbd6aaca47ceb97cb97047f4f615fd9a9"
integrity sha512-sETe50Nid+uuwg0/liEjhy9/o9ygjrYSOJJf1CzuC8xW1ni9wHMMrPx3vsHxYs21aE1+nW1hNgkCUw5kpw7Kjg==
dependencies:
"@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0"
@ -1091,12 +1091,12 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/pro@1.0.164":
version "1.0.164"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.164.tgz#780ae38893d0609c87bf51fe96cc2c35bbdb431a"
integrity sha512-PgF7q2vADPPYzet4Wdma+THWuQPrEnN1+TfRly4l0oS9SUxutog3hYn0TlPmPS0AHgrqG/1v65TcEdC4ucX8TA==
"@budibase/pro@1.0.167-alpha.3":
version "1.0.167-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.167-alpha.3.tgz#22fa274ffbd50e08dfe5ca4bb546f14d68ba02d4"
integrity sha512-y6bQmNxRRChGJUVRu/FEUdkBlYZlGpxMC8xBSd8oj1FDRdTofbL0it2XUEI3P8H5zBM0J33pw6x27dlWnrOItg==
dependencies:
"@budibase/backend-core" "1.0.164"
"@budibase/backend-core" "1.0.167-alpha.3"
node-fetch "^2.6.1"
"@budibase/standard-components@^0.9.139":

View File

@ -25,6 +25,5 @@ export const getInfo = async (ctx: any) => {
}
export const getQuotaUsage = async (ctx: any) => {
const usage = await quotas.getQuotaUsage()
ctx.body = usage
ctx.body = await quotas.getQuotaUsage()
}

View File

@ -75,8 +75,8 @@ const checkCurrentApp = ctx => {
const addSessionAttributesToUser = ctx => {
ctx.body.account = ctx.user.account
ctx.body.license = ctx.user.license
ctx.body.budibaseAccess = ctx.user.budibaseAccess
ctx.body.accountPortalAccess = ctx.user.accountPortalAccess
ctx.body.budibaseAccess = !!ctx.user.budibaseAccess
ctx.body.accountPortalAccess = !!ctx.user.accountPortalAccess
ctx.body.csrfToken = ctx.user.csrfToken
}

View File

@ -17,32 +17,38 @@ if (!LOADED && isDev() && !isTest()) {
}
module.exports = {
NODE_ENV: process.env.NODE_ENV,
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
PORT: process.env.PORT || process.env.WORKER_PORT,
CLUSTER_PORT: process.env.CLUSTER_PORT,
// auth
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
MINIO_URL: process.env.MINIO_URL,
COUCH_DB_URL: process.env.COUCH_DB_URL,
LOG_LEVEL: process.env.LOG_LEVEL,
JWT_SECRET: process.env.JWT_SECRET,
SALT_ROUNDS: process.env.SALT_ROUNDS,
REDIS_URL: process.env.REDIS_URL,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
// urls
MINIO_URL: process.env.MINIO_URL,
COUCH_DB_URL: process.env.COUCH_DB_URL,
REDIS_URL: process.env.REDIS_URL,
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
PLATFORM_URL: process.env.PLATFORM_URL,
APPS_URL: process.env.APPS_URL,
// ports
PORT: process.env.PORT || process.env.WORKER_PORT,
CLUSTER_PORT: process.env.CLUSTER_PORT,
// flags
NODE_ENV: process.env.NODE_ENV,
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
LOG_LEVEL: process.env.LOG_LEVEL,
MULTI_TENANCY: process.env.MULTI_TENANCY,
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
// smtp
SMTP_USER: process.env.SMTP_USER,
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
PLATFORM_URL: process.env.PLATFORM_URL,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
APPS_URL: process.env.APPS_URL,
_set(key, value) {
process.env[key] = value
module.exports[key] = value

View File

@ -293,10 +293,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.0.164":
version "1.0.164"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.164.tgz#08c111dcebf5c74159a3c18218c7b3a0716de4f6"
integrity sha512-lpMudezndUD1hHBLfT9LDNKCunj8rQNlaJb30/xggdIUvp718u/jVP54hXF26NYxXOTMZ0EvMwCsIS4AucJ1Mg==
"@budibase/backend-core@1.0.167-alpha.3":
version "1.0.167-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.167-alpha.3.tgz#bb82ba4bbd6aaca47ceb97cb97047f4f615fd9a9"
integrity sha512-sETe50Nid+uuwg0/liEjhy9/o9ygjrYSOJJf1CzuC8xW1ni9wHMMrPx3vsHxYs21aE1+nW1hNgkCUw5kpw7Kjg==
dependencies:
"@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0"
@ -321,12 +321,12 @@
uuid "^8.3.2"
zlib "^1.0.5"
"@budibase/pro@1.0.164":
version "1.0.164"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.164.tgz#780ae38893d0609c87bf51fe96cc2c35bbdb431a"
integrity sha512-PgF7q2vADPPYzet4Wdma+THWuQPrEnN1+TfRly4l0oS9SUxutog3hYn0TlPmPS0AHgrqG/1v65TcEdC4ucX8TA==
"@budibase/pro@1.0.167-alpha.3":
version "1.0.167-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.167-alpha.3.tgz#22fa274ffbd50e08dfe5ca4bb546f14d68ba02d4"
integrity sha512-y6bQmNxRRChGJUVRu/FEUdkBlYZlGpxMC8xBSd8oj1FDRdTofbL0it2XUEI3P8H5zBM0J33pw6x27dlWnrOItg==
dependencies:
"@budibase/backend-core" "1.0.164"
"@budibase/backend-core" "1.0.167-alpha.3"
node-fetch "^2.6.1"
"@cspotcode/source-map-consumer@0.8.0":