forked from tsai/budibase
committed by
GitHub
292 changed files with 10035 additions and 4276 deletions
@ -0,0 +1,9 @@ |
|||
packages/server/node_modules |
|||
packages/builder |
|||
packages/frontend-core |
|||
packages/backend-core |
|||
packages/worker/node_modules |
|||
packages/cli |
|||
packages/client |
|||
packages/bbui |
|||
packages/string-templates |
|||
@ -0,0 +1,94 @@ |
|||
{ |
|||
"version": "2", |
|||
"templates": [ |
|||
{ |
|||
"type": 3, |
|||
"title": "Budibase", |
|||
"categories": ["Tools"], |
|||
"description": "Build modern business apps in minutes", |
|||
"logo": "https://budibase.com/favicon.ico", |
|||
"platform": "linux", |
|||
"repository": { |
|||
"url": "https://github.com/Budibase/budibase", |
|||
"stackfile": "hosting/docker-compose.yaml" |
|||
}, |
|||
"env": [ |
|||
{ |
|||
"name": "MAIN_PORT", |
|||
"label": "Main port", |
|||
"default": "10000" |
|||
}, |
|||
{ |
|||
"name": "JWT_SECRET", |
|||
"label": "JWT secret", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "MINIO_ACCESS_KEY", |
|||
"label": "MinIO access key", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "MINIO_SECRET_KEY", |
|||
"label": "MinIO secret key", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "COUCH_DB_USER", |
|||
"default": "budibase", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "COUCH_DB_PASSWORD", |
|||
"label": "Couch DB password", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "REDIS_PASSWORD", |
|||
"label": "Redis password", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "INTERNAL_API_KEY", |
|||
"label": "Internal API key", |
|||
"default": "change-me" |
|||
}, |
|||
{ |
|||
"name": "APP_PORT", |
|||
"default": "4002", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "WORKER_PORT", |
|||
"default": "4003", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "MINIO_PORT", |
|||
"default": "4004", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "COUCH_DB_PORT", |
|||
"default": "4005", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "REDIS_PORT", |
|||
"default": "6379", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "WATCHTOWER_PORT", |
|||
"default": "6161", |
|||
"preset": true |
|||
}, |
|||
{ |
|||
"name": "BUDIBASE_ENVIRONMENT", |
|||
"default": "PRODUCTION", |
|||
"preset": true |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -1,2 +1,3 @@ |
|||
FROM nginx:latest |
|||
COPY .generated-nginx.prod.conf /etc/nginx/nginx.conf |
|||
COPY .generated-nginx.prod.conf /etc/nginx/nginx.conf |
|||
COPY error.html /usr/share/nginx/html/error.html |
|||
@ -0,0 +1,175 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Budibase</title> |
|||
<meta name="viewport" content="width=device-width,initial-scale=1"> |
|||
</head> |
|||
|
|||
<script> |
|||
function checkStatusButton() { |
|||
if (window.location.href.includes("budibase.app")) { |
|||
var button = document.getElementById("statusButton") |
|||
button.removeAttribute("hidden") |
|||
} |
|||
} |
|||
|
|||
function goToStatus() { |
|||
window.location.href = "https://status.budibase.com"; |
|||
} |
|||
function goHome() { |
|||
window.location.href = window.location.origin; |
|||
} |
|||
function getStatus() { |
|||
var http = new XMLHttpRequest() |
|||
var url = window.location.href |
|||
http.open('GET', url, true) |
|||
http.send() |
|||
http.onreadystatechange = (e) => { |
|||
var status = http.status |
|||
document.getElementById("status").innerHTML = status |
|||
|
|||
var message |
|||
if (status === 502) { |
|||
message = "Bad gateway. Please try again later." |
|||
} else if (status === 503) { |
|||
message = "Service Unavailable. Please try again later." |
|||
} else if (status === 504) { |
|||
message = "Gateway timeout. Please try again later." |
|||
} else { |
|||
message = "Please try again later." |
|||
} |
|||
|
|||
document.getElementById("message").innerHTML = message |
|||
} |
|||
} |
|||
|
|||
window.onload = function() { |
|||
checkStatusButton() |
|||
getStatus() |
|||
}; |
|||
|
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
:root { |
|||
--spectrum-global-color-gray-600: rgb(144,144,144); |
|||
--spectrum-global-color-gray-900: rgb(255,255,255); |
|||
--spectrum-global-color-gray-800: rgb(227,227,227); |
|||
--spectrum-global-color-static-blue-600: rgb(20,115,230); |
|||
--spectrum-global-color-static-blue-hover: rgb( 18, 103, 207); |
|||
} |
|||
|
|||
html, body { |
|||
background-color: #1a1a1a; |
|||
padding: 0; |
|||
margin: 0; |
|||
overflow: hidden; |
|||
color: #e7e7e7; |
|||
font-family: 'Roboto', sans-serif; |
|||
} |
|||
button { |
|||
color: #e7e7e7; |
|||
font-family: 'Roboto', sans-serif; |
|||
border: none; |
|||
font-size: 15px; |
|||
border-radius: 15px; |
|||
padding: 8px 22px; |
|||
} |
|||
button:hover { |
|||
cursor: pointer; |
|||
} |
|||
.main { |
|||
height: 100vh; |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
.info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: left; |
|||
} |
|||
|
|||
@media only screen and (max-width: 600px) { |
|||
.info { |
|||
align-items: center; |
|||
} |
|||
} |
|||
|
|||
.status { |
|||
color: var(--spectrum-global-color-gray-600) |
|||
} |
|||
.title { |
|||
font-weight: 400; |
|||
color: var(--spectrum-global-color-gray-900) |
|||
} |
|||
.message { |
|||
font-weight: 200; |
|||
color: var(--spectrum-global-color-gray-800) |
|||
} |
|||
.buttons { |
|||
display: flex; |
|||
flex-direction: row; |
|||
margin-top: 15px; |
|||
} |
|||
.homeButton { |
|||
background-color: var(--spectrum-global-color-static-blue-600); |
|||
} |
|||
.homeButton:hover { |
|||
background-color: var(--spectrum-global-color-static-blue-hover); |
|||
} |
|||
.statusButton { |
|||
background-color: transparent; |
|||
margin-left: 20px; |
|||
border: none; |
|||
} |
|||
.hero { |
|||
height: 160px; |
|||
width: 160px; |
|||
margin-right: 80px; |
|||
} |
|||
.content { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: flex-end; |
|||
justify-content: center; |
|||
} |
|||
|
|||
@media only screen and (max-width: 600px) { |
|||
.content { |
|||
flex-direction: column; |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
<script src=""> |
|||
</script> |
|||
|
|||
<body> |
|||
<div class="main"> |
|||
<div class="content"> |
|||
<div class="hero"> |
|||
<img src="https://raw.githubusercontent.com/Budibase/budibase/master/packages/builder/assets/bb-space-man.svg" alt="Budibase Logo"> |
|||
</div> |
|||
<div class="info"> |
|||
<div> |
|||
<h4 id="status" class="status"></h4> |
|||
<h1 class="title"> |
|||
Houston we have a problem! |
|||
</h1> |
|||
<h3 id="message" class="message"> |
|||
</h3> |
|||
</div> |
|||
<div class="buttons"> |
|||
<button class="homeButton" onclick=goHome()>Return home</button> |
|||
<button id="statusButton" class="statusButton" hidden="true" onclick=goToStatus()>Check out status</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
|||
@ -0,0 +1,97 @@ |
|||
FROM couchdb |
|||
|
|||
ENV COUCHDB_PASSWORD=budibase |
|||
ENV COUCHDB_USER=budibase |
|||
ENV COUCH_DB_URL=http://budibase:budibase@localhost:5984 |
|||
ENV BUDIBASE_ENVIRONMENT=PRODUCTION |
|||
ENV MINIO_URL=http://localhost:9000 |
|||
ENV REDIS_URL=localhost:6379 |
|||
ENV WORKER_URL=http://localhost:4002 |
|||
ENV INTERNAL_API_KEY=budibase |
|||
ENV JWT_SECRET=testsecret |
|||
ENV MINIO_ACCESS_KEY=budibase |
|||
ENV MINIO_SECRET_KEY=budibase |
|||
ENV SELF_HOSTED=1 |
|||
ENV CLUSTER_PORT=10000 |
|||
ENV REDIS_PASSWORD=budibase |
|||
ENV ARCHITECTURE=amd |
|||
ENV APP_PORT=4001 |
|||
ENV WORKER_PORT=4002 |
|||
|
|||
RUN apt-get update |
|||
RUN apt-get install software-properties-common wget nginx -y |
|||
RUN apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' |
|||
RUN apt-get update |
|||
|
|||
# setup nginx |
|||
ADD hosting/single/nginx.conf /etc/nginx |
|||
RUN mkdir /etc/nginx/logs |
|||
RUN useradd www |
|||
RUN touch /etc/nginx/logs/error.log |
|||
RUN touch /etc/nginx/logs/nginx.pid |
|||
|
|||
# install java |
|||
RUN apt-get install openjdk-8-jdk -y |
|||
|
|||
# setup nodejs |
|||
WORKDIR /nodejs |
|||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh |
|||
RUN bash /tmp/nodesource_setup.sh |
|||
RUN apt-get install nodejs |
|||
RUN npm install --global yarn |
|||
RUN npm install --global pm2 |
|||
|
|||
# setup redis |
|||
RUN apt install redis-server -y |
|||
|
|||
# setup server |
|||
WORKDIR /app |
|||
ADD packages/server . |
|||
RUN ls -al |
|||
RUN yarn |
|||
RUN yarn build |
|||
# Install client for oracle datasource |
|||
RUN apt-get install unzip libaio1 |
|||
RUN /bin/bash -e scripts/integrations/oracle/instantclient/linux/x86-64/install.sh |
|||
|
|||
# setup worker |
|||
WORKDIR /worker |
|||
ADD packages/worker . |
|||
RUN yarn |
|||
RUN yarn build |
|||
|
|||
# setup clouseau |
|||
WORKDIR / |
|||
RUN wget https://github.com/cloudant-labs/clouseau/releases/download/2.21.0/clouseau-2.21.0-dist.zip |
|||
RUN unzip clouseau-2.21.0-dist.zip |
|||
RUN mv clouseau-2.21.0 /opt/clouseau |
|||
RUN rm clouseau-2.21.0-dist.zip |
|||
|
|||
WORKDIR /opt/clouseau |
|||
RUN mkdir ./bin |
|||
ADD hosting/single/clouseau ./bin/ |
|||
ADD hosting/single/log4j.properties . |
|||
ADD hosting/single/clouseau.ini . |
|||
RUN chmod +x ./bin/clouseau |
|||
|
|||
# setup CouchDB |
|||
WORKDIR /opt/couchdb |
|||
ADD hosting/single/vm.args ./etc/ |
|||
|
|||
# setup minio |
|||
WORKDIR /minio |
|||
RUN wget https://dl.min.io/server/minio/release/linux-${ARCHITECTURE}64/minio |
|||
RUN chmod +x minio |
|||
|
|||
# setup runner file |
|||
WORKDIR / |
|||
ADD hosting/single/runner.sh . |
|||
RUN chmod +x ./runner.sh |
|||
|
|||
EXPOSE 10000 |
|||
VOLUME /opt/couchdb/data |
|||
VOLUME /minio |
|||
|
|||
# must set this just before running |
|||
ENV NODE_ENV=production |
|||
CMD ["./runner.sh"] |
|||
@ -0,0 +1,12 @@ |
|||
#!/bin/sh |
|||
/usr/bin/java -server \ |
|||
-Xmx2G \ |
|||
-Dsun.net.inetaddr.ttl=30 \ |
|||
-Dsun.net.inetaddr.negative.ttl=30 \ |
|||
-Dlog4j.configuration=file:/opt/clouseau/log4j.properties \ |
|||
-XX:OnOutOfMemoryError="kill -9 %p" \ |
|||
-XX:+UseConcMarkSweepGC \ |
|||
-XX:+CMSParallelRemarkEnabled \ |
|||
-classpath '/opt/clouseau/*' \ |
|||
com.cloudant.clouseau.Main \ |
|||
/opt/clouseau/clouseau.ini |
|||
@ -0,0 +1,13 @@ |
|||
[clouseau] |
|||
|
|||
; the name of the Erlang node created by the service, leave this unchanged |
|||
name=clouseau@127.0.0.1 |
|||
|
|||
; set this to the same distributed Erlang cookie used by the CouchDB nodes |
|||
cookie=monster |
|||
|
|||
; the path where you would like to store the search index files |
|||
dir=/opt/couchdb/data/search |
|||
|
|||
; the number of search indexes that can be open simultaneously |
|||
max_indexes_open=500 |
|||
@ -0,0 +1,4 @@ |
|||
log4j.rootLogger=debug, CONSOLE |
|||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender |
|||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout |
|||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %c [%p] %m%n |
|||
@ -0,0 +1,116 @@ |
|||
user www www; |
|||
error_log /etc/nginx/logs/error.log; |
|||
pid /etc/nginx/logs/nginx.pid; |
|||
worker_processes auto; |
|||
worker_rlimit_nofile 8192; |
|||
|
|||
events { |
|||
worker_connections 1024; |
|||
} |
|||
|
|||
http { |
|||
limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; |
|||
proxy_set_header Host $host; |
|||
charset utf-8; |
|||
sendfile on; |
|||
tcp_nopush on; |
|||
tcp_nodelay on; |
|||
server_tokens off; |
|||
types_hash_max_size 2048; |
|||
|
|||
# buffering |
|||
client_header_buffer_size 1k; |
|||
client_max_body_size 20M; |
|||
ignore_invalid_headers off; |
|||
proxy_buffering off; |
|||
|
|||
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
|||
'$status $body_bytes_sent "$http_referer" ' |
|||
'"$http_user_agent" "$http_x_forwarded_for"'; |
|||
|
|||
map $http_upgrade $connection_upgrade { |
|||
default "upgrade"; |
|||
} |
|||
|
|||
server { |
|||
listen 10000 default_server; |
|||
listen [::]:10000 default_server; |
|||
server_name _; |
|||
client_max_body_size 1000m; |
|||
ignore_invalid_headers off; |
|||
proxy_buffering off; |
|||
# port_in_redirect off; |
|||
|
|||
location /app { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location = / { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/(builder|app_) { |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/api/(system|admin|global)/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
} |
|||
|
|||
location /worker/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
rewrite ^/worker/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location /api/ { |
|||
# calls to the API are rate limited with bursting |
|||
limit_req zone=ratelimit burst=20 nodelay; |
|||
|
|||
# 120s timeout on API requests |
|||
proxy_read_timeout 120s; |
|||
proxy_connect_timeout 120s; |
|||
proxy_send_timeout 120s; |
|||
|
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location /db/ { |
|||
proxy_pass http://127.0.0.1:5984; |
|||
rewrite ^/db/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location / { |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_set_header X-Forwarded-Proto $scheme; |
|||
|
|||
proxy_connect_timeout 300; |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection ""; |
|||
chunked_transfer_encoding off; |
|||
proxy_pass http://127.0.0.1:9000; |
|||
} |
|||
|
|||
client_header_timeout 60; |
|||
client_body_timeout 60; |
|||
keepalive_timeout 60; |
|||
|
|||
# gzip |
|||
gzip on; |
|||
gzip_vary on; |
|||
gzip_proxied any; |
|||
gzip_comp_level 6; |
|||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
redis-server --requirepass $REDIS_PASSWORD & |
|||
/opt/clouseau/bin/clouseau & |
|||
/minio/minio server /minio & |
|||
/docker-entrypoint.sh /opt/couchdb/bin/couchdb & |
|||
/etc/init.d/nginx restart |
|||
pushd app |
|||
pm2 start --name app "yarn run:docker" |
|||
popd |
|||
pushd worker |
|||
pm2 start --name worker "yarn run:docker" |
|||
popd |
|||
sleep 10 |
|||
URL=http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984 |
|||
curl -X PUT ${URL}/_users |
|||
curl -X PUT ${URL}/_replicator |
|||
sleep infinity |
|||
@ -0,0 +1,32 @@ |
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|||
# use this file except in compliance with the License. You may obtain a copy of |
|||
# the License at |
|||
# |
|||
# http://www.apache.org/licenses/LICENSE-2.0 |
|||
# |
|||
# Unless required by applicable law or agreed to in writing, software |
|||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|||
# License for the specific language governing permissions and limitations under |
|||
# the License. |
|||
|
|||
# erlang cookie for clouseau security |
|||
-name couchdb@127.0.0.1 |
|||
-setcookie monster |
|||
|
|||
# Ensure that the Erlang VM listens on a known port |
|||
-kernel inet_dist_listen_min 9100 |
|||
-kernel inet_dist_listen_max 9100 |
|||
|
|||
# Tell kernel and SASL not to log anything |
|||
-kernel error_logger silent |
|||
-sasl sasl_error_logger false |
|||
|
|||
# Use kernel poll functionality if supported by emulator |
|||
+K true |
|||
|
|||
# Start a pool of asynchronous IO threads |
|||
+A 16 |
|||
|
|||
# Comment this line out to enable the interactive Erlang shell on startup |
|||
+Bd -noinput |
|||
@ -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", |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
import filterTests from "../support/filterTests" |
|||
|
|||
filterTests(['all'], () => { |
|||
context("Publish Application Workflow", () => { |
|||
before(() => { |
|||
cy.login() |
|||
cy.createTestApp() |
|||
}) |
|||
|
|||
it("Should reflect the unpublished status correctly", () => { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(1000) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Unpublished") |
|||
cy.get("svg[aria-label='GlobeStrike']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("Preview") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") |
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("not.exist") |
|||
}) |
|||
|
|||
it("Should publish an application and correctly reflect that", () => { |
|||
//Assuming the previous test was run and the unpublished app is open in edit mode.
|
|||
cy.get(".toprightnav button.spectrum-Button").contains("Publish").click({ force : true }) |
|||
|
|||
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']").should("be.visible") |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("Publish").click({ force : true }) |
|||
cy.wait(1000) |
|||
}); |
|||
|
|||
//Verify that the app url is presented correctly to the user
|
|||
cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']") |
|||
.should("be.visible") |
|||
.within(() => { |
|||
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests' |
|||
cy.get("[data-cy='deployed-app-url'] input").should('have.value', appUrl) |
|||
cy.get(".spectrum-Button").contains("Done").click({ force: true }) |
|||
}) |
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(1000) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Published") |
|||
cy.get("svg[aria-label='Globe']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("View app") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist").click({ force: true }) |
|||
|
|||
cy.get("[data-cy='publish-popover-menu']").should("be.visible") |
|||
.within(() => { |
|||
cy.get("[data-cy='publish-popover-action']").should("exist") |
|||
cy.get("button").contains("View app").should("exist") |
|||
cy.get(".publish-popover-message").should("have.text", "Last published a few seconds ago") |
|||
}) |
|||
}) |
|||
|
|||
it("Should unpublish an application from the top navigation and reflect the status change", () => { |
|||
//Assuming the previous test app exists and is published
|
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Published") |
|||
cy.get("svg[aria-label='Globe']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("View app") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
//The published status
|
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist") |
|||
.click({ force: true }) |
|||
|
|||
cy.get("[data-cy='publish-popover-menu']").should("be.visible") |
|||
cy.get("[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']") |
|||
.click({ force : true }) |
|||
|
|||
cy.get("[data-cy='unpublish-modal']").should("be.visible") |
|||
.within(() => { |
|||
cy.get(".confirm-wrap button").click({ force: true } |
|||
)}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") |
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
|
|||
cy.get(".appTable .app-status").eq(0).contains("Unpublished") |
|||
|
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,51 +1,103 @@ |
|||
import filterTests from "../support/filterTests" |
|||
|
|||
filterTests(['smoke', 'all'], () => { |
|||
context("Auto Screens UI", () => { |
|||
before(() => { |
|||
cy.login() |
|||
cy.createTestApp() |
|||
}) |
|||
|
|||
it("should generate internal table screens", () => { |
|||
// Create autogenerated screens from the internal table
|
|||
cy.createAutogeneratedScreens(["Cypress Tests"]) |
|||
// Confirm screens have been auto generated
|
|||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) |
|||
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id') |
|||
.and('contain', 'cypress-tests/new/row') |
|||
}) |
|||
|
|||
it("should generate multiple internal table screens at once", () => { |
|||
// Create a second internal table
|
|||
const initialTable = "Cypress Tests" |
|||
const secondTable = "Table Two" |
|||
cy.createTable(secondTable) |
|||
// Create autogenerated screens from the internal tables
|
|||
cy.createAutogeneratedScreens([initialTable, secondTable]) |
|||
// Confirm screens have been auto generated
|
|||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) |
|||
// Previously generated tables are suffixed with numbers - as expected
|
|||
cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id') |
|||
.and('contain', 'cypress-tests-2/new/row') |
|||
cy.get(".nav-items-container").contains("table-two").click() |
|||
cy.get(".nav-items-container").should('contain', 'table-two/:id') |
|||
.and('contain', 'table-two/new/row') |
|||
}) |
|||
|
|||
if (Cypress.env("TEST_ENV")) { |
|||
it("should generate data source screens", () => { |
|||
// Using MySQL data source for testing this
|
|||
const datasource = "MySQL" |
|||
// Select & configure MySQL data source
|
|||
cy.selectExternalDatasource(datasource) |
|||
cy.addDatasourceConfig(datasource) |
|||
// Create autogenerated screens from a MySQL table - MySQL contains books table
|
|||
cy.createAutogeneratedScreens(["books"]) |
|||
cy.get(".nav-items-container").contains("books").click() |
|||
cy.get(".nav-items-container").should('contain', 'books/:id') |
|||
.and('contain', 'books/new/row') |
|||
}) |
|||
} |
|||
context("Auto Screens UI", () => { |
|||
before(() => { |
|||
cy.login() |
|||
}) |
|||
|
|||
it("should disable the autogenerated screen options if no sources are available", () => { |
|||
cy.createApp("First Test App", false) |
|||
|
|||
cy.closeModal(); |
|||
|
|||
cy.contains("Design").click() |
|||
cy.get("[aria-label=AddCircle]").click() |
|||
cy.get(".spectrum-Modal").within(() => { |
|||
cy.get(".item.disabled").contains("Autogenerated screens") |
|||
cy.get(".confirm-wrap .spectrum-Button").should('be.disabled') |
|||
}) |
|||
|
|||
cy.deleteAllApps() |
|||
}); |
|||
|
|||
it("should not display incompatible sources", () => { |
|||
cy.createApp("Test App") |
|||
|
|||
cy.selectExternalDatasource("REST") |
|||
cy.selectExternalDatasource("S3") |
|||
cy.get(".spectrum-Modal").within(() => { |
|||
cy.get(".spectrum-Button").contains("Save and continue to query").click({ force : true }) |
|||
}) |
|||
|
|||
cy.navigateToAutogeneratedModal() |
|||
|
|||
cy.get('.data-source-entry').should('have.length', 1) |
|||
cy.get('.data-source-entry') |
|||
|
|||
cy.deleteAllApps() |
|||
}); |
|||
|
|||
it("should generate internal table screens", () => { |
|||
cy.createTestApp() |
|||
// Create Autogenerated screens from the internal table
|
|||
cy.createDatasourceScreen(["Cypress Tests"]) |
|||
// Confirm screens have been auto generated
|
|||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) |
|||
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id') |
|||
.and('contain', 'cypress-tests/new/row') |
|||
}) |
|||
|
|||
it("should generate multiple internal table screens at once", () => { |
|||
// Create a second internal table
|
|||
const initialTable = "Cypress Tests" |
|||
const secondTable = "Table Two" |
|||
cy.createTable(secondTable) |
|||
// Create Autogenerated screens from the internal tables
|
|||
cy.createDatasourceScreen([initialTable, secondTable]) |
|||
// Confirm screens have been auto generated
|
|||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) |
|||
// Previously generated tables are suffixed with numbers - as expected
|
|||
cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id') |
|||
.and('contain', 'cypress-tests-2/new/row') |
|||
cy.get(".nav-items-container").contains("table-two").click() |
|||
cy.get(".nav-items-container").should('contain', 'table-two/:id') |
|||
.and('contain', 'table-two/new/row') |
|||
}) |
|||
|
|||
it("should generate multiple internal table screens with the same screen access level", () => { |
|||
//The tables created in the previous step still exist
|
|||
cy.createTable("Table Three") |
|||
cy.createTable("Table Four") |
|||
cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin") |
|||
|
|||
cy.get(".nav-items-container").contains("table-three").click() |
|||
cy.get(".nav-items-container").should('contain', 'table-three/:id') |
|||
.and('contain', 'table-three/new/row') |
|||
|
|||
cy.get(".nav-items-container").contains("table-four").click() |
|||
cy.get(".nav-items-container").should('contain', 'table-four/:id') |
|||
.and('contain', 'table-four/new/row') |
|||
|
|||
//The access level should now be set to admin. Previous screens should be filtered.
|
|||
cy.get(".nav-items-container").contains("table-two").should('not.exist') |
|||
cy.get(".nav-items-container").contains("cypress-tests").should('not.exist') |
|||
}) |
|||
|
|||
if (Cypress.env("TEST_ENV")) { |
|||
it("should generate data source screens", () => { |
|||
// Using MySQL data source for testing this
|
|||
const datasource = "MySQL" |
|||
// Select & configure MySQL data source
|
|||
cy.selectExternalDatasource(datasource) |
|||
cy.addDatasourceConfig(datasource) |
|||
// Create Autogenerated screens from a MySQL table - MySQL contains books table
|
|||
cy.createDatasourceScreen(["books"]) |
|||
|
|||
cy.get(".nav-items-container").contains("books").click() |
|||
cy.get(".nav-items-container").should('contain', 'books/:id') |
|||
.and('contain', 'books/new/row') |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue