diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 70120b92fc..6d39d2844d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -92,6 +92,16 @@ then `cd ` into your local copy. ### 3. Install and Build +To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. + +#### Quick method + +`yarn setup` will check that all necessary components are installed and setup the repo for usage. + +#### Manual method + +The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). + `yarn` to install project dependencies `yarn bootstrap` will install all budibase modules and symlink them together using lerna. @@ -112,10 +122,17 @@ To run the budibase server and builder in dev mode (i.e. with live reloading): 1. Open a new console 2. `yarn dev` (from root) -3. Access the builder on http://localhost:4001/_builder/ +3. Access the builder on http://localhost:10000/builder This will enable watch mode for both the builder app, server, client library and any component libraries. +### 5. Cleanup + +If you wish to delete all the apps created in development and reset the environment then run the following: + +1. `yarn nuke:docker` will wipe all the Budibase services +2. `yarn dev` will restart all the services + ## Data Storage When you are running locally, budibase stores data on disk using [PouchDB](https://pouchdb.com/), as well as some JSON on local files. After setting up budibase, you can find all of this data in the `~/.budibase` directory. diff --git a/.github/cla/signatures.json b/.github/cla/signatures.json index 8b90a150f6..67d475a7a3 100644 --- a/.github/cla/signatures.json +++ b/.github/cla/signatures.json @@ -15,6 +15,22 @@ "created_at": "2021-04-14T16:20:04Z", "repoId": 190729906, "pullRequestNo": 1383 + }, + { + "name": "aptkingston", + "id": 9075550, + "comment_id": 830252031, + "created_at": "2021-04-30T17:37:37Z", + "repoId": 190729906, + "pullRequestNo": 1431 + }, + { + "name": "kevmodrome", + "id": 534488, + "comment_id": 830545461, + "created_at": "2021-05-01T05:27:53Z", + "repoId": 190729906, + "pullRequestNo": 1431 } ] } \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index e23b0be753..eda2e6131b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,6 +3,8 @@ "semi": false, "singleQuote": false, "trailingComma": "es5", + "arrowParens": "avoid", + "jsxBracketSameLine": false, "plugins": ["prettier-plugin-svelte"], - "svelteSortOrder" : "scripts-markup-styles" + "svelteSortOrder" : "options-scripts-markup-styles" } \ No newline at end of file diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index 9b4c353981..3b99ef796c 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -59,6 +59,7 @@ services: container_name: budi-redis-dev restart: always image: redis + command: redis-server --requirepass ${REDIS_PASSWORD} ports: - "${REDIS_PORT}:6379" volumes: diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 5e21cc9efd..6d9f64c07e 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -23,6 +23,8 @@ services: LOG_LEVEL: info SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 ENABLE_ANALYTICS: "true" + REDIS_URL: redis-service:6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} depends_on: - worker-service @@ -43,6 +45,8 @@ services: COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD} COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 SELF_HOST_KEY: ${HOSTING_KEY} + REDIS_URL: redis-service:6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} depends_on: - minio-service - couch-init @@ -100,8 +104,7 @@ services: redis-service: restart: always image: redis - ports: - - "${REDIS_PORT}:6379" + command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index a4e2a97118..054ceb9d3f 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -16,16 +16,16 @@ static_resources: - name: local_services domains: ["*"] routes: + # special case to redirect specifically the route path + # to the builder, if this were a prefix then it would break minio + - match: { path: "/" } + redirect: { path_redirect: "/builder/" } + - match: { prefix: "/db/" } route: cluster: couchdb-service prefix_rewrite: "/" - - match: { prefix: "/cache/" } - route: - cluster: redis-service - prefix_rewrite: "/" - - match: { prefix: "/api/admin/" } route: cluster: worker-dev @@ -38,6 +38,13 @@ static_resources: route: cluster: server-dev + # the below three cases are needed to make sure + # all traffic prefixed for the builder is passed through + # correctly. + - match: { path: "/" } + route: + cluster: builder-dev + - match: { prefix: "/builder/" } route: cluster: builder-dev @@ -47,10 +54,6 @@ static_resources: cluster: builder-dev prefix_rewrite: "/builder/" - # special case in dev to redirect no path to builder - - match: { path: "/" } - redirect: { path_redirect: "/builder/" } - # minio is on the default route because this works # best, minio + AWS SDK doesn't handle path proxy - match: { prefix: "/" } @@ -89,20 +92,6 @@ static_resources: address: couchdb-service port_value: 5984 - - name: redis-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: redis-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: redis-service - port_value: 6379 - - name: server-dev connect_timeout: 0.25s type: strict_dns diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 1fbd2070ff..95db418352 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -21,7 +21,6 @@ static_resources: cluster: app-service prefix_rewrite: "/" - # special case for presenting our static self hosting page - match: { path: "/" } route: cluster: app-service @@ -41,11 +40,6 @@ static_resources: cluster: worker-service prefix_rewrite: "/" - - match: { prefix: "/cache/" } - route: - cluster: redis-service - prefix_rewrite: "/" - - match: { prefix: "/db/" } route: cluster: couchdb-service @@ -117,18 +111,3 @@ static_resources: address: couchdb-service port_value: 5984 - - name: redis-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: redis-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: redis-service - port_value: 6379 - - diff --git a/hosting/hosting.properties b/hosting/hosting.properties index 138e66d629..4297ec60a1 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -12,6 +12,7 @@ MINIO_ACCESS_KEY=budibase MINIO_SECRET_KEY=budibase COUCH_DB_PASSWORD=budibase COUCH_DB_USER=budibase +REDIS_PASSWORD=budibase # This section contains variables that do not need to be altered under normal circumstances APP_PORT=4002 diff --git a/hosting/scripts/setup.js b/hosting/scripts/setup.js new file mode 100755 index 0000000000..c62ac14f29 --- /dev/null +++ b/hosting/scripts/setup.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +const os = require("os") +const exec = require("child_process").exec +const fs = require("fs") +const platform = os.platform() + +const windows = platform === "win32" +const mac = platform === "darwin" +const linux = platform === "linux" + +function execute(command) { + return new Promise(resolve => { + exec(command, (err, stdout) => resolve(linux ? !!stdout : true)) + }) +} + +async function commandExistsUnix(command) { + const unixCmd = `command -v ${command} 2>/dev/null && { echo >&1 ${command}; exit 0; }` + return execute(command) +} + +async function commandExistsWindows(command) { + if (/[\x00-\x1f<>:"|?*]/.test(command)) { + return false + } + return execute(`where ${command}`) +} + +function commandExists(command) { + return windows ? commandExistsWindows(command) : commandExistsUnix(command) +} + +async function init() { + const docker = commandExists("docker") + const dockerCompose = commandExists("docker-compose") + if (docker && dockerCompose) { + console.log("Docker installed - continuing.") + return + } + if (mac) { + console.log( + "Please install docker by visiting: https://docs.docker.com/docker-for-mac/install/" + ) + } else if (windows) { + console.log( + "Please install docker by visiting: https://docs.docker.com/docker-for-windows/install/" + ) + } else if (linux) { + console.log("Beginning automated linux installation.") + await execute(`./hosting/scripts/linux/get-docker.sh`) + await execute(`./hosting/scripts/linux/get-docker-compose.sh`) + } else { + console.error( + "Platform unknown - please look online for information about installing docker for our OS." + ) + } + console.log("Once installation complete please re-run the setup script.") + process.exit(-1) +} +init() diff --git a/package.json b/package.json index f98624812f..4d26be9941 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "prettier-plugin-svelte": "^2.2.0", "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", - "svelte": "^3.30.0" + "svelte": "^3.38.2" }, "scripts": { + "setup": "./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", "bootstrap": "lerna link && lerna bootstrap", "build": "lerna run build", "initialise": "lerna run initialise", diff --git a/packages/auth/package.json b/packages/auth/package.json index b4f4b1cb33..56b904c966 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,18 +1,27 @@ { "name": "@budibase/auth", - "version": "0.0.1", + "version": "0.18.6", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", "license": "AGPL-3.0", "dependencies": { + "aws-sdk": "^2.901.0", "bcryptjs": "^2.4.3", + "ioredis": "^4.27.1", "jsonwebtoken": "^8.5.1", "koa-passport": "^4.1.4", + "node-fetch": "^2.6.1", "passport-google-auth": "^1.0.2", "passport-google-oauth": "^2.0.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", - "uuid": "^8.3.2" + "sanitize-s3-objectkey": "^0.0.1", + "tar-fs": "^2.1.1", + "uuid": "^8.3.2", + "zlib": "^1.0.5" + }, + "devDependencies": { + "ioredis-mock": "^5.5.5" } } diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 393e03e492..eb3c593c12 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -123,7 +123,7 @@ const getConfigParams = ({ type, group, user }, otherProps = {}) => { * @param {Object} scopes - the type, group and userID scopes of the configuration. * @returns The most granular configuration document based on the scope. */ -const determineScopedConfig = async function(db, { type, user, group }) { +const getScopedFullConfig = async function (db, { type, user, group }) { const response = await db.allDocs( getConfigParams( { type, user, group }, @@ -132,31 +132,40 @@ const determineScopedConfig = async function(db, { type, user, group }) { } ) ) - const configs = response.rows.map(row => { + + function determineScore(row) { const config = row.doc // Config is specific to a user and a group if (config._id.includes(generateConfigID({ type, user, group }))) { - config.score = 4 + return 4 } else if (config._id.includes(generateConfigID({ type, user }))) { // Config is specific to a user only - config.score = 3 + return 3 } else if (config._id.includes(generateConfigID({ type, group }))) { // Config is specific to a group only - config.score = 2 + return 2 } else if (config._id.includes(generateConfigID({ type }))) { // Config is specific to a type only - config.score = 1 + return 1 } - return config - }) + return 0 + } // Find the config with the most granular scope based on context - const scopedConfig = configs.sort((a, b) => b.score - a.score)[0] + const scopedConfig = response.rows.sort( + (a, b) => determineScore(a) - determineScore(b) + )[0] - return scopedConfig + return scopedConfig && scopedConfig.doc } +async function getScopedConfig(db, params) { + const configDoc = await getScopedFullConfig(db, params) + return configDoc && configDoc.config ? configDoc.config : configDoc +} + +exports.getScopedConfig = getScopedConfig exports.generateConfigID = generateConfigID exports.getConfigParams = getConfigParams -exports.determineScopedConfig = determineScopedConfig +exports.getScopedFullConfig = getScopedFullConfig diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index 3a5c81ea8b..db24aaafcc 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -1,5 +1,19 @@ +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + module.exports = { JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, SALT_ROUNDS: process.env.SALT_ROUNDS, + REDIS_URL: process.env.REDIS_URL, + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, + MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, + MINIO_URL: process.env.MINIO_URL, + isTest, } diff --git a/packages/auth/src/hashing.js b/packages/auth/src/hashing.js index 65976fc1f3..7f3af2855f 100644 --- a/packages/auth/src/hashing.js +++ b/packages/auth/src/hashing.js @@ -13,6 +13,6 @@ exports.compare = async (data, encrypted) => { return bcrypt.compare(data, encrypted) } -exports.newid = function() { +exports.newid = function () { return v4().replace(/-/g, "") } diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index 348f911f80..d2ba86a524 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -28,6 +28,14 @@ module.exports = { setDB(pouch) }, db: require("./db/utils"), + redis: { + Client: require("./redis"), + utils: require("./redis/utils"), + }, + objectStore: { + ...require("./objectStore"), + ...require("./objectStore/utils"), + }, utils: { ...require("./utils"), ...require("./hashing"), diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index d64c30a70a..5d8d4e7e13 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -3,11 +3,35 @@ const database = require("../db") const { getCookie, clearCookie } = require("../utils") const { StaticDatabases } = require("../db/utils") -module.exports = (noAuthPatterns = []) => { - const regex = new RegExp(noAuthPatterns.join("|")) +const PARAM_REGEX = /\/:(.*?)\//g + +function buildNoAuthRegex(patterns) { + return patterns.map(pattern => { + const isObj = typeof pattern === "object" && pattern.route + const method = isObj ? pattern.method : "GET" + let route = isObj ? pattern.route : pattern + + const matches = route.match(PARAM_REGEX) + if (matches) { + for (let match of matches) { + route = route.replace(match, "/.*/") + } + } + return { regex: new RegExp(route), method } + }) +} + +module.exports = (noAuthPatterns = [], opts) => { + const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : [] return async (ctx, next) => { // the path is not authenticated - if (regex.test(ctx.request.url)) { + const found = noAuthOptions.find(({ regex, method }) => { + return ( + regex.test(ctx.request.url) && + ctx.request.method.toLowerCase() === method.toLowerCase() + ) + }) + if (found != null) { return next() } try { @@ -30,10 +54,14 @@ module.exports = (noAuthPatterns = []) => { if (ctx.isAuthenticated !== true) { ctx.isAuthenticated = false } - return next() } catch (err) { - ctx.throw(err.status || 403, err) + // allow configuring for public access + if (opts && opts.publicAllowed) { + ctx.isAuthenticated = false + } else { + ctx.throw(err.status || 403, err) + } } } } diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index 968dfa3e93..407772ebf0 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -51,7 +51,7 @@ async function authenticate(token, tokenSecret, profile, done) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport Google Strategy */ -exports.strategyFactory = async function(config) { +exports.strategyFactory = async function (config) { try { const { clientID, clientSecret, callbackURL } = config diff --git a/packages/auth/src/middleware/passport/jwt.js b/packages/auth/src/middleware/passport/jwt.js index fdff3f3cfc..ed7d179482 100644 --- a/packages/auth/src/middleware/passport/jwt.js +++ b/packages/auth/src/middleware/passport/jwt.js @@ -3,12 +3,12 @@ const env = require("../../environment") exports.options = { secretOrKey: env.JWT_SECRET, - jwtFromRequest: function(ctx) { + jwtFromRequest: function (ctx) { return ctx.cookies.get(Cookies.Auth) }, } -exports.authenticate = async function(jwt, done) { +exports.authenticate = async function (jwt, done) { try { return done(null, jwt) } catch (err) { diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 5b8bf307d7..0f5cb82606 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -15,7 +15,7 @@ exports.options = {} * @param {*} done - callback from passport to return user information and errors * @returns The authenticated user, or errors if they occur */ -exports.authenticate = async function(email, password, done) { +exports.authenticate = async function (email, password, done) { if (!email) return done(null, false, "Email Required.") if (!password) return done(null, false, "Password Required.") diff --git a/packages/auth/src/objectStore/index.js b/packages/auth/src/objectStore/index.js new file mode 100644 index 0000000000..a78253f90a --- /dev/null +++ b/packages/auth/src/objectStore/index.js @@ -0,0 +1,240 @@ +const sanitize = require("sanitize-s3-objectkey") +const AWS = require("aws-sdk") +const stream = require("stream") +const fetch = require("node-fetch") +const tar = require("tar-fs") +const zlib = require("zlib") +const { promisify } = require("util") +const { join } = require("path") +const fs = require("fs") +const env = require("../environment") +const { budibaseTempDir, ObjectStoreBuckets } = require("./utils") +const { v4 } = require("uuid") + +const streamPipeline = promisify(stream.pipeline) +// use this as a temporary store of buckets that are being created +const STATE = { + bucketCreationPromises: {}, +} + +const CONTENT_TYPE_MAP = { + html: "text/html", + css: "text/css", + js: "application/javascript", +} +const STRING_CONTENT_TYPES = [ + CONTENT_TYPE_MAP.html, + CONTENT_TYPE_MAP.css, + CONTENT_TYPE_MAP.js, +] + +function publicPolicy(bucketName) { + return { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { + AWS: ["*"], + }, + Action: "s3:GetObject", + Resource: [`arn:aws:s3:::${bucketName}/*`], + }, + ], + } +} + +const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL] + +/** + * Gets a connection to the object store using the S3 SDK. + * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. + * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. + * @constructor + */ +exports.ObjectStore = bucket => { + AWS.config.update({ + accessKeyId: env.MINIO_ACCESS_KEY, + secretAccessKey: env.MINIO_SECRET_KEY, + }) + const config = { + s3ForcePathStyle: true, + signatureVersion: "v4", + params: { + Bucket: bucket, + }, + } + if (env.MINIO_URL) { + config.endpoint = env.MINIO_URL + } + return new AWS.S3(config) +} + +/** + * Given an object store and a bucket name this will make sure the bucket exists, + * if it does not exist then it will create it. + */ +exports.makeSureBucketExists = async (client, bucketName) => { + try { + await client + .headBucket({ + Bucket: bucketName, + }) + .promise() + } catch (err) { + const promises = STATE.bucketCreationPromises + if (promises[bucketName]) { + await promises[bucketName] + } else if (err.statusCode === 404) { + // bucket doesn't exist create it + promises[bucketName] = client + .createBucket({ + Bucket: bucketName, + }) + .promise() + await promises[bucketName] + delete promises[bucketName] + // public buckets are quite hidden in the system, make sure + // no bucket is set accidentally + if (PUBLIC_BUCKETS.includes(bucketName)) { + await client + .putBucketPolicy({ + Bucket: bucketName, + Policy: JSON.stringify(publicPolicy(bucketName)), + }) + .promise() + } + } else { + throw err + } + } +} + +/** + * Uploads the contents of a file given the required parameters, useful when + * temp files in use (for example file uploaded as an attachment). + */ +exports.upload = async ({ bucket, filename, path, type, metadata }) => { + const extension = [...filename.split(".")].pop() + const fileBytes = fs.readFileSync(path) + + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const config = { + // windows file paths need to be converted to forward slashes for s3 + Key: sanitize(filename).replace(/\\/g, "/"), + Body: fileBytes, + ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()], + } + if (metadata) { + config.Metadata = metadata + } + return objectStore.upload(config).promise() +} + +/** + * Similar to the upload function but can be used to send a file stream + * through to the object store. + */ +exports.streamUpload = async (bucket, filename, stream) => { + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const params = { + Bucket: bucket, + Key: sanitize(filename).replace(/\\/g, "/"), + Body: stream, + } + return objectStore.upload(params).promise() +} + +/** + * retrieves the contents of a file from the object store, if it is a known content type it + * will be converted, otherwise it will be returned as a buffer stream. + */ +exports.retrieve = async (bucket, filepath) => { + const objectStore = exports.ObjectStore(bucket) + const params = { + Bucket: bucket, + Key: sanitize(filepath).replace(/\\/g, "/"), + } + const response = await objectStore.getObject(params).promise() + // currently these are all strings + if (STRING_CONTENT_TYPES.includes(response.ContentType)) { + return response.Body.toString("utf8") + } else { + return response.Body + } +} + +/** + * Same as retrieval function but puts to a temporary file. + */ +exports.retrieveToTmp = async (bucket, filepath) => { + const data = await exports.retrieve(bucket, filepath) + const outputPath = join(budibaseTempDir(), v4()) + fs.writeFileSync(outputPath, data) + return outputPath +} + +exports.deleteFolder = async (bucket, folder) => { + const client = exports.ObjectStore(bucket) + const listParams = { + Bucket: bucket, + Prefix: folder, + } + + let response = await client.listObjects(listParams).promise() + if (response.Contents.length === 0) { + return + } + const deleteParams = { + Bucket: bucket, + Delete: { + Objects: [], + }, + } + + response.Contents.forEach(content => { + deleteParams.Delete.Objects.push({ Key: content.Key }) + }) + + response = await client.deleteObjects(deleteParams).promise() + // can only empty 1000 items at once + if (response.Deleted.length === 1000) { + return exports.deleteFolder(bucket, folder) + } +} + +exports.uploadDirectory = async (bucket, localPath, bucketPath) => { + let uploads = [] + const files = fs.readdirSync(localPath, { withFileTypes: true }) + for (let file of files) { + const path = join(bucketPath, file.name) + const local = join(localPath, file.name) + if (file.isDirectory()) { + uploads.push(exports.uploadDirectory(bucket, local, path)) + } else { + uploads.push( + exports.streamUpload(bucket, path, fs.createReadStream(local)) + ) + } + } + await Promise.all(uploads) +} + +exports.downloadTarball = async (url, bucket, path) => { + const response = await fetch(url) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + const tmpPath = join(budibaseTempDir(), path) + await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) + if (!env.isTest()) { + await exports.uploadDirectory(bucket, tmpPath, path) + } + // return the temporary path incase there is a use for it + return tmpPath +} diff --git a/packages/auth/src/objectStore/utils.js b/packages/auth/src/objectStore/utils.js new file mode 100644 index 0000000000..41fa18d99e --- /dev/null +++ b/packages/auth/src/objectStore/utils.js @@ -0,0 +1,13 @@ +const { join } = require("path") +const { tmpdir } = require("os") + +exports.ObjectStoreBuckets = { + BACKUPS: "backups", + APPS: "prod-budi-app-assets", + TEMPLATES: "templates", + GLOBAL: "global", +} + +exports.budibaseTempDir = function () { + return join(tmpdir(), ".budibase") +} diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js new file mode 100644 index 0000000000..dc670c07fb --- /dev/null +++ b/packages/auth/src/redis/index.js @@ -0,0 +1,152 @@ +const env = require("../environment") +// ioredis mock is all in memory +const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") +const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") + +const CLUSTERED = false + +// for testing just generate the client once +let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null + +/** + * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise + * will return the ioredis client which will be ready to use. + * @return {Promise} The ioredis client. + */ +function init() { + return new Promise((resolve, reject) => { + // testing uses a single in memory client + if (env.isTest()) { + return resolve(CLIENT) + } + // if a connection existed, close it and re-create it + if (CLIENT) { + CLIENT.disconnect() + CLIENT = null + } + const { opts, host, port } = getRedisOptions(CLUSTERED) + if (CLUSTERED) { + CLIENT = new Redis.Cluster([{ host, port }], opts) + } else { + CLIENT = new Redis(opts) + } + CLIENT.on("end", err => { + reject(err) + }) + CLIENT.on("error", err => { + reject(err) + }) + CLIENT.on("connect", () => { + resolve(CLIENT) + }) + }) +} + +/** + * Utility function, takes a redis stream and converts it to a promisified response - + * this can only be done with redis streams because they will have an end. + * @param stream A redis stream, specifically as this type of stream will have an end. + * @return {Promise} The final output of the stream + */ +function promisifyStream(stream) { + return new Promise((resolve, reject) => { + const outputKeys = new Set() + stream.on("data", keys => { + keys.forEach(key => { + outputKeys.add(key) + }) + }) + stream.on("error", err => { + reject(err) + }) + stream.on("end", async () => { + const keysArray = Array.from(outputKeys) + try { + let getPromises = [] + for (let key of keysArray) { + getPromises.push(CLIENT.get(key)) + } + const jsonArray = await Promise.all(getPromises) + resolve( + keysArray.map(key => ({ + key: removeDbPrefix(key), + value: JSON.parse(jsonArray.shift()), + })) + ) + } catch (err) { + reject(err) + } + }) + }) +} + +class RedisWrapper { + constructor(db) { + this._db = db + } + + async init() { + this._client = await init() + return this + } + + async finish() { + this._client.disconnect() + } + + async scan() { + const db = this._db, + client = this._client + let stream + if (CLUSTERED) { + let node = client.nodes("master") + stream = node[0].scanStream({ match: db + "-*", count: 100 }) + } else { + stream = client.scanStream({ match: db + "-*", count: 100 }) + } + return promisifyStream(stream) + } + + async get(key) { + const db = this._db, + client = this._client + let response = await client.get(addDbPrefix(db, key)) + // overwrite the prefixed key + if (response != null && response.key) { + response.key = key + } + // if its not an object just return the response + try { + return JSON.parse(response) + } catch (err) { + return response + } + } + + async store(key, value, expirySeconds = null) { + const db = this._db, + client = this._client + if (typeof value === "object") { + value = JSON.stringify(value) + } + const prefixedKey = addDbPrefix(db, key) + await client.set(prefixedKey, value) + if (expirySeconds) { + await client.expire(prefixedKey, expirySeconds) + } + } + + async delete(key) { + const db = this._db, + client = this._client + await client.del(addDbPrefix(db, key)) + } + + async clear() { + const db = this._db + let items = await this.scan(db) + await Promise.all(items.map(obj => this.delete(db, obj.key))) + } +} + +module.exports = RedisWrapper diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js new file mode 100644 index 0000000000..bd4a762e1d --- /dev/null +++ b/packages/auth/src/redis/utils.js @@ -0,0 +1,46 @@ +const env = require("../environment") + +const SLOT_REFRESH_MS = 2000 +const CONNECT_TIMEOUT_MS = 10000 +const SEPARATOR = "-" +const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL +const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD + +exports.Databases = { + PW_RESETS: "pwReset", + INVITATIONS: "invitation", +} + +exports.getRedisOptions = (clustered = false) => { + const [host, port] = REDIS_URL.split(":") + const opts = { + connectTimeout: CONNECT_TIMEOUT_MS, + } + if (clustered) { + opts.redisOptions = {} + opts.redisOptions.tls = {} + opts.redisOptions.password = REDIS_PASSWORD + opts.slotsRefreshTimeout = SLOT_REFRESH_MS + opts.dnsLookup = (address, callback) => callback(null, address) + } else { + opts.host = host + opts.port = port + opts.password = REDIS_PASSWORD + } + return { opts, host, port } +} + +exports.addDbPrefix = (db, key) => { + return `${db}${SEPARATOR}${key}` +} + +exports.removeDbPrefix = key => { + let parts = key.split(SEPARATOR) + if (parts.length >= 2) { + parts.shift() + return parts.join(SEPARATOR) + } else { + // return the only part + return parts[0] + } +} diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 10507410b1..a0ba0d25b5 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -105,6 +105,12 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } +/** + * Given an email address this will use a view to search through + * all the users to find one with this email address. + * @param {string} email the email to lookup the user by. + * @return {Promise} + */ exports.getGlobalUserByEmail = async email => { const db = getDB(StaticDatabases.GLOBAL.name) try { diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock index c3066ebdc1..80625a9345 100644 --- a/packages/auth/yarn.lock +++ b/packages/auth/yarn.lock @@ -36,6 +36,21 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +aws-sdk@^2.901.0: + version "2.901.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.901.0.tgz#96b387778cf2b3537383fba04994e815f1fab4d4" + integrity sha512-prmUjg4mjguamnwaXMdm/g1xtnT9515cjSaV/MhBsMUVzhe66EX7dLwiA7Jo8qUlwFMyCVIcp/2T+6KwJ9sQgQ== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -46,6 +61,16 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.0.2, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base64url@3.x.x: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -63,16 +88,60 @@ bcryptjs@^2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -80,6 +149,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -92,11 +166,23 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +denque@^1.1.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -112,6 +198,18 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -137,6 +235,20 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fengari-interop@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87" + integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ== + +fengari@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb" + integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g== + dependencies: + readline-sync "^1.4.9" + sprintf-js "^1.1.1" + tmp "^0.0.33" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -151,6 +263,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -216,16 +333,68 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.13, ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ioredis-mock@^5.5.5: + version "5.5.5" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256" + integrity sha512-7SxCAwNtDLC8IFDptqIhOC7ajp3fciVtCrXOEOkpyjPboAGRQkJbnpNPy1NYORoWi+0/iOtUPUQckSKtSQj4DA== + dependencies: + fengari "^0.1.4" + fengari-interop "^0.1.2" + lodash "^4.17.21" + minimatch "^3.0.4" + standard-as-callback "^2.1.0" + +ioredis@^4.27.1: + version "4.27.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.1.tgz#4ef947b455a1b995baa4b0d7e2c4e4f75f746421" + integrity sha512-PaFNFeBbOcEYHXAdrJuy7uesJcyvzStTM1aYMchTuky+VgKqDbXhnTJHaDsjAwcTwPx8Asatx+l2DW8zZ2xlsQ== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -296,6 +465,16 @@ koa-passport@^4.1.4: dependencies: passport "^0.4.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -336,7 +515,7 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.14.0: +lodash@^4.14.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -358,11 +537,33 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@^0.7.1: version "0.7.6" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" @@ -378,6 +579,23 @@ oauth@0.9.x: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + passport-google-auth@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" @@ -471,6 +689,19 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -481,6 +712,42 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readline-sync@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + request@^2.72.0, request@^2.74.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -507,7 +774,7 @@ request@^2.72.0, request@^2.74.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -safe-buffer@^5.0.1, safe-buffer@^5.1.2: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -517,11 +784,31 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sanitize-s3-objectkey@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/sanitize-s3-objectkey/-/sanitize-s3-objectkey-0.0.1.tgz#efa9887cd45275b40234fb4bb12fc5754fe64e7e" + integrity sha512-ZTk7aqLxy4sD40GWcYWoLfbe05XLmkKvh6vGKe13ADlei24xlezcvjgKy1qRArlaIbIMYaqK7PCalvZtulZlaQ== + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +sprintf-js@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -537,11 +824,51 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + string-template@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -574,11 +901,29 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -597,3 +942,26 @@ verror@1.10.0: assert-plus "^1.0.0" core-util-is "1.0.2" extsprintf "^1.2.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + +zlib@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" + integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 573e115377..056f4229cb 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -27,7 +27,7 @@ "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-svelte": "^7.1.0", "rollup-plugin-terser": "^7.0.2", - "svelte": "^3.37.0" + "svelte": "^3.38.2" }, "keywords": [ "svelte" diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index a4bfd29c83..4bb592aa26 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -42,12 +42,14 @@ class="spectrum-ActionButton spectrum-ActionButton--size{size}" {disabled} on:longPress - on:click|preventDefault> + on:click|preventDefault +> {#if longPressable} {/if} @@ -56,7 +58,8 @@ class="spectrum-Icon spectrum-Icon--size{size}" focusable="false" aria-hidden="true" - aria-label={icon}> + aria-label={icon} + > {/if} diff --git a/packages/bbui/src/ActionGroup/ActionGroup.svelte b/packages/bbui/src/ActionGroup/ActionGroup.svelte index 514b473521..43d8cd8de5 100644 --- a/packages/bbui/src/ActionGroup/ActionGroup.svelte +++ b/packages/bbui/src/ActionGroup/ActionGroup.svelte @@ -8,7 +8,7 @@ // Attaches a spectrum-ActionGroup-item class to buttons inside the div function group(element) { const buttons = Array.from(element.getElementsByTagName("button")) - buttons.forEach((button) => { + buttons.forEach(button => { button.classList.add("spectrum-ActionGroup-item") }) } diff --git a/packages/bbui/src/ActionMenu/ActionMenu.svelte b/packages/bbui/src/ActionMenu/ActionMenu.svelte index 6947e69a7e..8f17f260a8 100644 --- a/packages/bbui/src/ActionMenu/ActionMenu.svelte +++ b/packages/bbui/src/ActionMenu/ActionMenu.svelte @@ -4,6 +4,7 @@ import Menu from "../Menu/Menu.svelte" export let disabled = false + export let align = "left" let anchor let dropdown @@ -31,7 +32,7 @@
- + diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte index d2aef72446..7a6ad5f004 100644 --- a/packages/bbui/src/Avatar/Avatar.svelte +++ b/packages/bbui/src/Avatar/Avatar.svelte @@ -1,12 +1,53 @@ -Avatar +{#if url} + Avatar +{:else} +
+ {getInitials(name)} +
+{/if} + + diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index 6e2b63adbd..da4d405f02 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -23,13 +23,15 @@ class:active class="spectrum-Button spectrum-Button--size{size.toUpperCase()}" {disabled} - on:click|preventDefault> + on:click|preventDefault +> {#if icon} {/if} diff --git a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte index 51055363a2..b845a770e1 100644 --- a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte +++ b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte @@ -1,15 +1,19 @@ -
- -
\ No newline at end of file +
+ +
diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index b5af512475..3658fdb7fe 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -37,8 +37,10 @@
- {title} - + {title} + + +
diff --git a/packages/bbui/src/Drawer/DrawerContent.svelte b/packages/bbui/src/Drawer/DrawerContent.svelte index b41b1ab529..955abc0da4 100644 --- a/packages/bbui/src/Drawer/DrawerContent.svelte +++ b/packages/bbui/src/Drawer/DrawerContent.svelte @@ -28,11 +28,9 @@ border-right: var(--border-light); overflow: auto; } - .sidebar::-webkit-scrollbar { display: none; } - .main { font-family: var(--font-sans); } diff --git a/packages/bbui/src/Form/Combobox.svelte b/packages/bbui/src/Form/Combobox.svelte index 380465792b..b718921325 100644 --- a/packages/bbui/src/Form/Combobox.svelte +++ b/packages/bbui/src/Form/Combobox.svelte @@ -35,5 +35,6 @@ {placeholder} {getOptionLabel} {getOptionValue} - on:change={onChange} /> + on:change={onChange} + /> diff --git a/packages/bbui/src/Form/Core/Checkbox.svelte b/packages/bbui/src/Form/Core/Checkbox.svelte index bc9b9a9fc7..7d564d2a9d 100644 --- a/packages/bbui/src/Form/Core/Checkbox.svelte +++ b/packages/bbui/src/Form/Core/Checkbox.svelte @@ -17,27 +17,31 @@ diff --git a/packages/bbui/src/Form/Core/Combobox.svelte b/packages/bbui/src/Form/Core/Combobox.svelte index 5ec93c87ac..2a5b2b7cf4 100644 --- a/packages/bbui/src/Form/Core/Combobox.svelte +++ b/packages/bbui/src/Form/Core/Combobox.svelte @@ -51,12 +51,14 @@ class="spectrum-InputGroup" class:is-focused={open || focus} class:is-invalid={!!error} - class:is-disabled={disabled}> + class:is-disabled={disabled} +>
+ class:is-focused={open || focus} + > (focus = true)} @@ -65,18 +67,21 @@ {value} {disabled} {placeholder} - class="spectrum-Textfield-input spectrum-InputGroup-input" /> + class="spectrum-Textfield-input spectrum-InputGroup-input" + />
@@ -84,7 +89,8 @@
(open = false)} />
+ class="spectrum-Popover spectrum-Popover--bottom is-open" + >
    {#if options && Array.isArray(options)} {#each options as option} @@ -94,13 +100,16 @@ role="option" aria-selected="true" tabindex="0" - on:click={() => selectOption(getOptionValue(option))}> - {getOptionLabel(option)} + on:click={() => selectOption(getOptionValue(option))} + > + {getOptionLabel(option)} diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index a3a2fefb38..f76e42a55a 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -64,7 +64,8 @@ on:close={onClose} options={flatpickrOptions} on:change={handleChange} - element={`#${flatpickrId}`}> + element={`#${flatpickrId}`} +>
    + aria-haspopup="true" + >
    + class:is-invalid={!!error} + > {#if !!error} {/if} @@ -94,7 +98,8 @@ class="spectrum-Textfield-input spectrum-InputGroup-input" {placeholder} {id} - {value} /> + {value} + />
    diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index e6addd4e42..5b5c72b809 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -15,6 +15,8 @@ export let fileSizeLimit = BYTES_IN_MB * 20 export let processFiles = null export let handleFileTooLarge = null + export let gallery = true + export let error = null const dispatch = createEventDispatcher() const imageExtensions = [ @@ -52,6 +54,8 @@ const newValue = [...value, ...processedFiles] dispatch("change", newValue) selectedImageIdx = newValue.length - 1 + } else { + dispatch("change", fileList) } } @@ -94,45 +98,68 @@
    {#if selectedImage} -
diff --git a/packages/bbui/src/Menu/Section.svelte b/packages/bbui/src/Menu/Section.svelte index 54ae4123be..5aea52b029 100644 --- a/packages/bbui/src/Menu/Section.svelte +++ b/packages/bbui/src/Menu/Section.svelte @@ -1,9 +1,10 @@ +
  • -{heading} -
      - -
    -
  • \ No newline at end of file + {heading} +
      + +
    + diff --git a/packages/bbui/src/Menu/Separator.svelte b/packages/bbui/src/Menu/Separator.svelte index 187fab2347..3118c7262f 100644 --- a/packages/bbui/src/Menu/Separator.svelte +++ b/packages/bbui/src/Menu/Separator.svelte @@ -1 +1 @@ - \ No newline at end of file +
    {#if showCancelButton || showConfirmButton}
    + class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter" + > {#if showCancelButton} @@ -58,7 +64,8 @@ cta {...$$restProps} disabled={confirmDisabled} - on:click={confirm}> + on:click={confirm} + > {confirmText} {/if} @@ -73,6 +80,10 @@
    - \ No newline at end of file + + + diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index bea421fd07..e07dc28ccf 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -37,7 +37,8 @@ use:clickOutside={hide} on:keydown={handleEscape} class="spectrum-Popover is-open" - role="presentation"> + role="presentation" + > diff --git a/packages/bbui/src/ProgressBar/ProgressBar.svelte b/packages/bbui/src/ProgressBar/ProgressBar.svelte index bca427b7ff..c24d69e67c 100644 --- a/packages/bbui/src/ProgressBar/ProgressBar.svelte +++ b/packages/bbui/src/ProgressBar/ProgressBar.svelte @@ -33,22 +33,14 @@ > {#if $$slots}
    {/if} {#if value}
    {Math.round($progress)}%
    diff --git a/packages/bbui/src/ProgressCircle/ProgressCircle.svelte b/packages/bbui/src/ProgressCircle/ProgressCircle.svelte index ba1dc10965..711517ec7b 100644 --- a/packages/bbui/src/ProgressCircle/ProgressCircle.svelte +++ b/packages/bbui/src/ProgressCircle/ProgressCircle.svelte @@ -1,29 +1,62 @@ -
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    +
    +
    +
    +
    - +
    diff --git a/packages/bbui/src/SideNavigation/Item.svelte b/packages/bbui/src/SideNavigation/Item.svelte index 17b83d1605..f50270dfbd 100644 --- a/packages/bbui/src/SideNavigation/Item.svelte +++ b/packages/bbui/src/SideNavigation/Item.svelte @@ -1,30 +1,44 @@ -
  • - {#if heading} - +
  • + {#if heading} + + {/if} + + {#if icon} + {/if} - - {#if icon} - - {/if} - - - {#if multilevel && $$slots.subnav} -
      - -
    - {/if} -
  • \ No newline at end of file + + + {#if multilevel && $$slots.subnav} +
      + +
    + {/if} + diff --git a/packages/bbui/src/SideNavigation/Navigation.svelte b/packages/bbui/src/SideNavigation/Navigation.svelte index 1400f0a508..d4d090ec1f 100644 --- a/packages/bbui/src/SideNavigation/Navigation.svelte +++ b/packages/bbui/src/SideNavigation/Navigation.svelte @@ -1,12 +1,12 @@ \ No newline at end of file + diff --git a/packages/bbui/src/Stores/notifications.js b/packages/bbui/src/Stores/notifications.js index 761d8531ec..ce0c9921f0 100644 --- a/packages/bbui/src/Stores/notifications.js +++ b/packages/bbui/src/Stores/notifications.js @@ -50,12 +50,7 @@ export const createNotificationStore = () => { } function id() { - return ( - "_" + - Math.random() - .toString(36) - .substr(2, 9) - ) + return "_" + Math.random().toString(36).substr(2, 9) } export const notifications = createNotificationStore() diff --git a/packages/bbui/src/Styleguide/Body.svelte b/packages/bbui/src/Styleguide/Body.svelte deleted file mode 100644 index 6fc583bc91..0000000000 --- a/packages/bbui/src/Styleguide/Body.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - -

    - -

    - - diff --git a/packages/bbui/src/Styleguide/Borders.svench b/packages/bbui/src/Styleguide/Borders.svench deleted file mode 100644 index 8e2c1f6dab..0000000000 --- a/packages/bbui/src/Styleguide/Borders.svench +++ /dev/null @@ -1,175 +0,0 @@ - - -

    Borders

    -

    Budibase has 2 border variables, light and dark.

    -

    Light is for layouts.

    -

    Dark is for components.

    -border: var(--border-light); -
    -border: var(--border-dark); -
    - -
    - -

    Border Radius

    -

    Budibase has 5 border-radius variables:

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameVarrempxVisual
    Extra Smallvar(--border-radius-xs)0.1252
    Extra small
    Smallvar(--border-radius-s)0.355.6px
    Small
    Mediumvar(--border-radius-m)0.58
    Medium
    Largevar(--border-radius-l)116
    Large
    Extra Largevar(--border-radius-xl)1001600
    Extra large
    - - diff --git a/packages/bbui/src/Styleguide/Budibase-logo.svelte b/packages/bbui/src/Styleguide/Budibase-logo.svelte deleted file mode 100644 index b8dbde3ac0..0000000000 --- a/packages/bbui/src/Styleguide/Budibase-logo.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - diff --git a/packages/bbui/src/Styleguide/Color.svench b/packages/bbui/src/Styleguide/Color.svench deleted file mode 100644 index 0c29669a4c..0000000000 --- a/packages/bbui/src/Styleguide/Color.svench +++ /dev/null @@ -1,201 +0,0 @@ - - - -
    white
    -
    var(--grey-1)
    -
    var(--grey-2)
    -
    var(--grey-3)
    -
    var(--grey-4)
    -
    var(--grey-5)
    -
    var(--grey-6)
    -
    var(--grey-7)
    -
    var(--grey-8)
    -
    var(--grey-9)
    -
    var(--ink)
    -
    - - -
    var(--blue-light)
    -
    var(--blue)
    -
    var(--blue-dark)
    -
    - - -
    var(--yellow-light)
    -
    var(--yellow)
    -
    var(--yellow-dark)
    -
    - - -
    var(--red-light)
    -
    var(--red)
    -
    var(--red-dark)
    -
    - - -
    var(--orange-light)
    -
    var(--orange)
    -
    var(--orange-dark)
    -
    - - -
    var(--green-light)
    -
    var(--green)
    -
    var(--green-dark)
    -
    - - -
    var(--purple-light)
    -
    var(--purple)
    -
    var(--purple-dark)
    -
    - - diff --git a/packages/bbui/src/Styleguide/Heading.svelte b/packages/bbui/src/Styleguide/Heading.svelte deleted file mode 100644 index d08f1389d8..0000000000 --- a/packages/bbui/src/Styleguide/Heading.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - -

    - -

    - - diff --git a/packages/bbui/src/Styleguide/Logo.svelte b/packages/bbui/src/Styleguide/Logo.svelte deleted file mode 100644 index 6164fe11eb..0000000000 --- a/packages/bbui/src/Styleguide/Logo.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/packages/bbui/src/Styleguide/Logo.svench b/packages/bbui/src/Styleguide/Logo.svench deleted file mode 100644 index f71bf9f262..0000000000 --- a/packages/bbui/src/Styleguide/Logo.svench +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/packages/bbui/src/Styleguide/Spacing.svench b/packages/bbui/src/Styleguide/Spacing.svench deleted file mode 100644 index ca727da18e..0000000000 --- a/packages/bbui/src/Styleguide/Spacing.svench +++ /dev/null @@ -1,191 +0,0 @@ - - -

    2 Scales, 1 Spatial System

    -

    Budibase has 2 scales, spacing and layout, and follows a 4pt grid system.

    -

    Spacing is used for smaller, more refined spacing needs, specifically within the context of a component.

    -

    Layout is used for laying out your page and positing components within that page.

    -

    Spacing and layout will mostly exist alongside margin and padding.

    -

    margin: var(--spacing-s);

    -padding: var(--spacing-s) var(--spacing-l); - -
    - -

    Spacing

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    VarrempxVisual
    var(--spacing-xs)0.254
    var(--spacing-s)0.58
    var(--spacing-m).7512
    var(--spacing-l)116
    var(--spacing-xl)1.2520
    - -
    - -

    Layout

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    VarrempxVisual
    var(--layout-xs)1.2520
    var(--layout-s)1.524
    var(--layout-m)232
    var(--layout-l)348
    var(--layout-xl)464
    - - \ No newline at end of file diff --git a/packages/bbui/src/Styleguide/Typography.svench b/packages/bbui/src/Styleguide/Typography.svench deleted file mode 100644 index dfab94eb84..0000000000 --- a/packages/bbui/src/Styleguide/Typography.svench +++ /dev/null @@ -1,227 +0,0 @@ - - - - -

    Typography

    -

    - Budibase uses Inter as its typeface and three types can be used, - heading - , - body - and - label -

    -

    Each type has 5 sizes, xs, s, m l and xl.

    -

    Each type is available in the colors white, grey or black.

    - - -
    -
    - Heading Extra Large Black - - Heading Large Black - - Heading Medium Black - - Heading Small Black - - Heading Extra Small Black -
    - -
    - Heading Extra Large Grey - - Heading Large Grey - - Heading Medium Grey - - Heading Small Grey - - Heading Extra Small Grey -
    - -
    - Heading Extra Large White - - Heading Large White - - Heading Medium White - - Heading Small White - - Heading Extra Small White -
    - -
    - Heading Extra Large Black Line Height - - Heading Large Black Line Height - - Heading Medium Black Line Height - - Heading Small Black Line Height - - Heading Extra Small Black Line Height -
    -
    -
    - - -
    -
    - Body Extra Large Black - - Body Large Black - - Body Medium Black - - Body Small Black - - Body Extra Small Black -
    - -
    - Body Extra Large Black Line-height - - Body Large Black Line-height - - Body Medium Black Line-height - - Body Small Black Line-height - - Body Extra Small Black Line-height -
    - -
    - Body Extra Large Grey - - Body Large Grey - - Body Medium Grey - - Body Small Grey - - Body Extra Small Grey -
    - -
    - Body Extra Large White - - Body Large White - - Body Medium White - - Body Small White - - Body Extra Small White -
    -
    -
    - - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    - -
    -
    - - -
    - -
    - - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    -
    diff --git a/packages/bbui/src/Table/BooleanRenderer.svelte b/packages/bbui/src/Table/BooleanRenderer.svelte index a71e28cb91..4e1aa3b4e1 100644 --- a/packages/bbui/src/Table/BooleanRenderer.svelte +++ b/packages/bbui/src/Table/BooleanRenderer.svelte @@ -5,24 +5,28 @@