diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..3bced5a534 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,57 @@ +{ + "name": "Corteza Dev", + "dockerComposeFile": [ + "../docker-compose.dev.yaml" + ], + "service": "corteza", + "workspaceFolder": "/workspace", + "overrideCommand": false, + "customizations": { + "vscode": { + "extensions": [ + "golang.go", + "vue.volar", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "editorconfig.editorconfig" + ], + "settings": { + "go.toolsManagement.autoUpdate": false, + "go.useLanguageServer": true, + "go.lintTool": "staticcheck", + "go.lintOnSave": "package", + "go.formatTool": "goimports", + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "[vue]": { + "editor.defaultFormatter": "Vue.volar" + }, + "editor.formatOnSave": true, + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 1000 + } + } + }, + "portsAttributes": { + "8000": { + "label": "Corteza Server", + "onAutoForward": "notify" + }, + "5432": { + "label": "PostgreSQL", + "onAutoForward": "ignore" + }, + "6379": { + "label": "Redis", + "onAutoForward": "ignore" + } + }, + "remoteUser": "root", + "runArgs": [ + "--network=corteza-dev" + ] +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..b5c5f63773 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Git +.gitignore + +# IDE +.idea +.vscode +*.iml +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw* + +# Node_modules - the main cause of 4.55GB build context! +# These get reinstalled inside the container +**/node_modules +**/node_modules/**/* + +# OS +.DS_Store +Thumbs.db + +# Docker files (not needed in build context) +Dockerfile +Dockerfile.* +docker-compose* +.dockerignore + +# Logs +*.log +logs/ diff --git a/.env.docker.dev b/.env.docker.dev new file mode 100644 index 0000000000..07b106cd01 --- /dev/null +++ b/.env.docker.dev @@ -0,0 +1,79 @@ +# Corteza Development Environment Variables +# Copy this to .env.docker.dev and modify as needed + +# =========================================== +# Core Settings +# =========================================== +NODE_ENV=development +ENVIRONMENT=dev +LOCALE_DEVELOPMENT_MODE=true +DEBUG=true +LOG_LEVEL=debug + +HTTP_WEBAPP_ENABLED=true +HTTP_ADDR=localhost:8000 +DOMAIN_WEBAPP=localhost:8000 +DOMAIN=localhost:8000 +# =========================================== +# Server Configuration +# =========================================== +#HTTP_ADDR=0.0.0.0:8000 +CORTEZA_SERVER_URL=http://host.docker.internal:8000 + +# =========================================== +# Database Configuration (PostgreSQL) +# =========================================== +DATABASE_URL=postgresql://corteza:corteza@postgres:5432/corteza?sslmode=disable + +# =========================================== +# Redis Configuration +# =========================================== +REDIS_URL=redis://redis:6379 + +# =========================================== +# Authentication & Security +# =========================================== +AUTH_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6 +JWT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6 +JWT_ALG=HS256 + +# =========================================== +# Storage +# =========================================== +STORAGE_PATH=/data + +# =========================================== +# Corredor (Automation) +# =========================================== +CORREDOR_ADDR=corredor:80 +CORREDOR_ENABLED=false + +# =========================================== +# Queue +# =========================================== +QUEUE_TYPE=redis +QUEUE_URL=redis://redis:6379 + +# =========================================== +# Cache +# =========================================== +CACHE_TYPE=redis +CACHE_URL=redis://redis:6379 + +# =========================================== +# Email / SMTP +# =========================================== +SMTP_HOST=host.docker.internal +SMTP_PORT=1025 +SMTP_FROM="Corteza Dev " + +# =========================================== +# Feature Flags +# =========================================== +DISCOVERY_ENABLED=true +FEDERATION_ENABLED=true + +# =========================================== +# Gin (Hot Reload) Configuration +# =========================================== +GIN_LADDR=0.0.0.0 diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000000..996166815b --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,78 @@ +# Dev Container for Corteza - Full Stack Development +# Ubuntu 22.04 with Go, Node.js, and all required dependencies + +FROM ubuntu:22.04 + +# Avoid interactive prompts +ENV DEBIAN_FRONTEND=noninteractive + +# Set environment variables +ENV GOPATH=/root/go +ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH +ENV CGO_ENABLED=1 +ENV GOFLAGS=-trimpath + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + wget \ + git \ + build-essential \ + gcc \ + g++ \ + make \ + pkg-config \ + libpq-dev \ + libsqlite3-dev \ + libmysqlclient-dev \ + libssl-dev \ + libcairo2-dev \ + libpango1.0-dev \ + libjpeg-dev \ + libgif-dev \ + librsvg2-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Go 1.24.1 +RUN wget -q https://go.dev/dl/go1.24.1.linux-amd64.tar.gz && \ + rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go1.24.1.linux-amd64.tar.gz && \ + rm go1.24.1.linux-amd64.tar.gz + +RUN go version + +# Install Node.js 22.x +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs && \ + rm -rf /var/lib/apt/lists/* + +RUN node --version && npm --version + +# Install Yarn globally +RUN npm install -g yarn + +# Install Dart Sass 1.85.1 +RUN wget -q https://github.com/sass/dart-sass/releases/download/1.85.1/dart-sass-1.85.1-linux-x64.tar.gz && \ + tar -xzf dart-sass-1.85.1-linux-x64.tar.gz -C /opt/ && \ + rm dart-sass-1.85.1-linux-x64.tar.gz && \ + ln -s /opt/dart-sass/sass /usr/local/bin/sass + +# Install essential Go tools for development (one at a time to avoid conflicts) +RUN go install github.com/codegangsta/gin@latest || true +RUN go install github.com/golang/mock/mockgen@latest || true +RUN go install honnef.co/go/tools/cmd/staticcheck@latest || true +RUN go install github.com/golang/protobuf/protoc-gen-go@latest || true +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest || true +RUN go install github.com/goware/statik@latest || true + +# CUE has version conflicts - install separately +RUN go install cuelang.org/go/cmd/cue@v0.5.0 || true + +# Working directory +WORKDIR /workspace + +# Expose ports +EXPOSE 8000 5432 6379 + +# Default shell +CMD ["/bin/bash"] diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 0000000000..46d7631df8 --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,180 @@ +# Multi-stage Dockerfile for local development +# Builds Corteza from source (server + webapps) instead of downloading releases + +# Build arguments (matching release.yml) +ARG GO_VERSION=1.24.1 +ARG NODE_VERSION=22 +ARG SASS_VERSION=1.85.1 + +################################################################################ +# Stage 1: Build Go server +################################################################################ +FROM golang:${GO_VERSION}-bookworm AS server-build + +# Install build dependencies +RUN apt-get -y update && apt-get install -y git make gcc libc6-dev && rm -rf /var/lib/apt/lists/* + +WORKDIR /build/server + +# Copy Go modules +COPY server/go.mod server/go.sum ./ +RUN go mod download + +# Copy source code +COPY server/ ./ + +# Copy locale directory for language embedding +COPY locale/ ./locale/ + +# Copy English locale files to pkg/locale/src/en (embedding languages into binary) +RUN mkdir -p pkg/locale/src/en && cp -r locale/en/* pkg/locale/src/en/ + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) +# These files need to be in the server build directory +COPY README.md LICENSE DCO ./ +RUN CGO_ENABLED=1 go build -o corteza-server ./cmd/corteza + +################################################################################ +# Stage 2: Build Web Console +################################################################################ +FROM node:${NODE_VERSION}-bookworm AS webconsole-build + +WORKDIR /build/webconsole + +COPY server/webconsole/package.json server/webconsole/yarn.lock ./ +RUN yarn install + +COPY server/webconsole/ ./ +RUN yarn build + +################################################################################ +# Stage 3a: Install WebApp Dependencies (Base Layer) +################################################################################ +FROM node:${NODE_VERSION}-bookworm AS webapps-deps + +# Install git, make, vue cli-service +RUN apt-get -y update && apt-get install -y git make && rm -rf /var/lib/apt/lists/* +RUN npm install -g @vue/cli-service + +WORKDIR /build + +# Copy entire client directory structure to maintain relative paths for file: dependencies +COPY client/ ./client/ +COPY lib/ ./lib/ + +# Copy .git directory for version info (needed by vue.config.js) +COPY .git ./.git/ + +# Install libraries for all webapps (sequential but only once) +RUN cd lib && make fresh +################################################################################ +# Stage 3b: Build WebApps in Parallel (via BuildKit) +################################################################################ +# Each webapp builds in its own stage - BuildKit runs these in parallel + +FROM webapps-deps AS webapp-admin-build +WORKDIR /build/client/web/admin +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-compose-build +WORKDIR /build/client/web/compose +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-discovery-build +WORKDIR /build/client/web/discovery +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-one-build +WORKDIR /build/client/web/one +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-privacy-build +WORKDIR /build/client/web/privacy +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-reporter-build +WORKDIR /build/client/web/reporter +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +FROM webapps-deps AS webapp-workflow-build +WORKDIR /build/client/web/workflow +RUN rm -rf node_modules yarn.lock && yarn && yarn cdeps +RUN yarn build + +################################################################################ +# Stage 4: Final runtime image +################################################################################ +FROM ubuntu:22.04 AS runtime + +# Build arguments need to be redeclared in each stage +ARG SASS_VERSION=1.85.1 + +# Install runtime dependencies (matching official Dockerfile) +RUN apt-get -y update && apt-get install -y \ + ca-certificates \ + curl \ + sqlite3 \ + libsqlite3-dev \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Download and install Dart Sass (matching official Dockerfile) +RUN curl -sOL https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz && \ + tar -xzf dart-sass-${SASS_VERSION}-linux-x64.tar.gz && \ + mv dart-sass /opt/dart-sass && \ + rm dart-sass-${SASS_VERSION}-linux-x64.tar.gz + +ENV PATH="/opt/dart-sass:/corteza/bin:${PATH}" + +# Environment variables - defaults (can be overridden by docker-compose) +ENV STORAGE_PATH="/data" +ENV CORREDOR_ADDR="corredor:80" +ENV HTTP_ADDR="0.0.0.0:80" +ENV HTTP_WEBAPP_ENABLED="true" +ENV ENVIRONMENT="dev" + +# Set working directory +WORKDIR /corteza + +# Copy server binary +COPY --from=server-build /build/server/corteza-server ./bin/corteza-server + +# Copy web console +COPY --from=webconsole-build /build/webconsole/dist ./webconsole + +# Copy webapps from parallel build stages +COPY --from=webapp-one-build /build/client/web/one/dist ./webapp +COPY --from=webapp-admin-build /build/client/web/admin/dist ./webapp/admin +COPY --from=webapp-compose-build /build/client/web/compose/dist ./webapp/compose +COPY --from=webapp-one-build /build/client/web/one/dist ./webapp/one +COPY --from=webapp-workflow-build /build/client/web/workflow/dist ./webapp/workflow +COPY --from=webapp-discovery-build /build/client/web/discovery/dist ./webapp/discovery +COPY --from=webapp-privacy-build /build/client/web/privacy/dist ./webapp/privacy +COPY --from=webapp-reporter-build /build/client/web/reporter/dist ./webapp/reporter + +# Copy provision files +COPY --from=server-build /build/server/provision ./provision + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) +COPY --from=server-build /build/server/README.md . +COPY --from=server-build /build/server/LICENSE . + +# Volume for data +VOLUME /data + +# Health check +HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \ + CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1 + +EXPOSE 80 + +ENTRYPOINT ["./bin/corteza-server"] +CMD ["serve-api"] diff --git a/Dockerfile.local-min b/Dockerfile.local-min new file mode 100644 index 0000000000..6fc6112416 --- /dev/null +++ b/Dockerfile.local-min @@ -0,0 +1,120 @@ +# Multi-stage Dockerfile for local development +# Builds Corteza from source (server + webapps) instead of downloading releases + +# Build arguments (matching release.yml) +ARG GO_VERSION=1.24.1 +ARG NODE_VERSION=22 +ARG SASS_VERSION=1.85.1 + +################################################################################ +# Stage 1: Build Go server +################################################################################ +FROM golang:${GO_VERSION}-bookworm AS server-build + +# Install build dependencies +RUN apt-get -y update && apt-get install -y git make gcc libc6-dev && rm -rf /var/lib/apt/lists/* + +WORKDIR /build/server + +# Copy Go modules +COPY server/go.mod server/go.sum ./ +RUN go mod download + +# Copy source code +COPY server/ ./ + +# Copy locale directory for language embedding +COPY locale/ ./locale/ + +# Copy English locale files to pkg/locale/src/en (embedding languages into binary) +RUN mkdir -p pkg/locale/src/en && cp -r locale/en/* pkg/locale/src/en/ + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) +# These files need to be in the server build directory +COPY README.md LICENSE DCO ./ +RUN CGO_ENABLED=1 go build -o corteza-server ./cmd/corteza + +################################################################################ +# Stage 2: Build Web Console +################################################################################ +FROM node:${NODE_VERSION}-bookworm AS webconsole-build + +WORKDIR /build/webconsole + +COPY server/webconsole/package.json server/webconsole/yarn.lock ./ +RUN yarn install + +COPY server/webconsole/ ./ +RUN yarn build + +################################################################################ +# Stage 4: Final runtime image +################################################################################ +FROM ubuntu:22.04 AS runtime + +# Build arguments need to be redeclared in each stage +ARG SASS_VERSION=1.85.1 + +# Install runtime dependencies (matching official Dockerfile) +RUN apt-get -y update && apt-get install -y \ + ca-certificates \ + curl \ + sqlite3 \ + libsqlite3-dev \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Download and install Dart Sass (matching official Dockerfile) +RUN curl -sOL https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz && \ + tar -xzf dart-sass-${SASS_VERSION}-linux-x64.tar.gz && \ + mv dart-sass /opt/dart-sass && \ + rm dart-sass-${SASS_VERSION}-linux-x64.tar.gz + +ENV PATH="/opt/dart-sass:/corteza/bin:${PATH}" + +# Environment variables - defaults (can be overridden by docker-compose) +ENV STORAGE_PATH="/data" +ENV CORREDOR_ADDR="corredor:80" +ENV HTTP_ADDR="0.0.0.0:80" +ENV HTTP_WEBAPP_ENABLED="true" +ENV ENVIRONMENT="dev" + +# Set working directory +WORKDIR /corteza + +# Copy server binary +COPY --from=server-build /build/server/corteza-server ./bin/corteza-server + +# Copy web console +COPY --from=webconsole-build /build/webconsole/dist ./webconsole +COPY client/ ./client/ +# Copy webapps from parallel build stages +COPY client/web/one/dist ./webapp +COPY client/web/admin/dist ./webapp/admin +COPY client/web/compose/dist ./webapp/compose +COPY client/web/one/dist ./webapp/one +COPY client/web/workflow/dist ./webapp/workflow +COPY client/web/discovery/dist ./webapp/discovery +COPY client/web/privacy/dist ./webapp/privacy +COPY client/web/reporter/dist ./webapp/reporter + +# Copy provision files +COPY --from=server-build /build/server/provision ./provision + +# Copy essential files from root (matching CI: cp *.md DCO LICENSE server/) +COPY --from=server-build /build/server/README.md . +COPY --from=server-build /build/server/LICENSE . + +# Volume for data +VOLUME /data + +# Health check +HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \ + CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1 + +EXPOSE 80 + +ENTRYPOINT ["./bin/corteza-server"] +CMD ["serve-api"] diff --git a/client/web/compose/src/components/Admin/Page/Builder/Selector.vue b/client/web/compose/src/components/Admin/Page/Builder/Selector.vue index 5ebf7b4232..fce73f5e7d 100644 --- a/client/web/compose/src/components/Admin/Page/Builder/Selector.vue +++ b/client/web/compose/src/components/Admin/Page/Builder/Selector.vue @@ -33,20 +33,21 @@
@@ -54,9 +55,9 @@ + + + + + + + + + + + + + + + + [], }, - existingBlocks: { + existingLayoutBlocks: { + type: Array, + default: () => [], + }, + + selectableGlobalBlocks: { type: Array, default: () => [], }, @@ -111,7 +159,8 @@ export default { return { current: undefined, - selectedExistingBlock: undefined, + selectedLayoutBlock: undefined, + selectedGlobalBlock: undefined, types: [ { @@ -220,9 +269,26 @@ export default { setDefaultValues () { this.current = undefined - this.selectedExistingBlock = undefined + this.selectedLayoutBlock = undefined + this.selectedGlobalBlock = undefined this.types = [] }, + + fetchBlockData (blockID) { + if (blockID.includes('-')) { + return this.selectableGlobalBlocks.find((b) => b.blockID === blockID) + } + + return this.existingLayoutBlocks.find((b) => b.blockID === blockID) + }, + + selectBlock (block, clone = false) { + if (clone) { + this.$emit('select', this.fetchBlockData(block).clone()) + } else { + this.$emit('select', this.fetchBlockData(block)) + } + }, }, } - + \ No newline at end of file diff --git a/client/web/compose/src/components/PageBlocks/Configurator.vue b/client/web/compose/src/components/PageBlocks/Configurator.vue index 36361d3575..daacd824b3 100644 --- a/client/web/compose/src/components/PageBlocks/Configurator.vue +++ b/client/web/compose/src/components/PageBlocks/Configurator.vue @@ -184,6 +184,17 @@ > {{ $t('general.border.show') }} + + + {{ $t('general.globalBlock.label') }} + @@ -405,6 +416,8 @@ export default { options: [], }, abortableRequests: [], + initialBlockState: undefined, + initialBlockID: undefined, } }, @@ -476,6 +489,8 @@ export default { }, created () { + this.initialBlockState = this.block.meta.namespaceID + this.initialBlockID = this.block.blockID this.fetchRoles() }, @@ -519,6 +534,16 @@ export default { } this.abortableRequests = [] }, + + updateGlobalState (value) { + this.block.blockID = this.initialBlockState === value ? this.initialBlockID : NoID + + if (value) { + this.block.meta.namespaceID = value + } else { + this.block.meta.namespaceID = undefined + } + }, }, } diff --git a/client/web/compose/src/store/namespace.js b/client/web/compose/src/store/namespace.js index d95f44d9f0..18a30ba5c2 100644 --- a/client/web/compose/src/store/namespace.js +++ b/client/web/compose/src/store/namespace.js @@ -37,6 +37,16 @@ export default function (ComposeAPI) { set (state) { return state.set }, + + getNamespaceBlocksByID (state, { getByID }) { + return (ID) => ((getByID(ID) || {}).blocks || []).map((b) => { + const ns = getByID(ID) + const block = compose.PageBlockMaker(b) + block.blockID = `${ns.namespaceID}-${b.blockID}` + + return block + }) + }, }, actions: { diff --git a/client/web/compose/src/views/Admin/Modules/List.vue b/client/web/compose/src/views/Admin/Modules/List.vue index f6b79411e1..6b7d030e96 100644 --- a/client/web/compose/src/views/Admin/Modules/List.vue +++ b/client/web/compose/src/views/Admin/Modules/List.vue @@ -1,4 +1,4 @@ -" @@ -366,6 +381,7 @@ diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000000..b688699797 --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,78 @@ +# Docker Compose for Corteza Full Stack Development +# Run with: docker-compose -f docker-compose.dev.yaml up -d + +name: corteza-dev + +services: + # Corteza Development Container + corteza: + build: + context: . + dockerfile: Dockerfile.dev + container_name: corteza-dev + command: tail -f /dev/null + ports: + - "127.0.0.1:8000:8000" + volumes: + # Mount only the server directory for development + - ./:/workspace/ + # Use named volume for Go module cache (much faster than host mount) + - go_cache:/go/pkg/mod + - go_build_cache:/root/.cache/go-build + environment: + # Tell Go to skip module cache for mounted paths + GOPROXY: direct + GOSUMDB: off + + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - corteza-dev + # PostgreSQL Database + postgres: + image: postgres:15 + container_name: corteza-postgres + environment: + POSTGRES_USER: corteza + POSTGRES_PASSWORD: corteza + POSTGRES_DB: corteza + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - corteza-dev + healthcheck: + test: ["CMD-SHELL", "pg_isready -U corteza"] + interval: 5s + timeout: 5s + retries: 5 + + # Redis Cache + redis: + image: redis:7-alpine + container_name: corteza-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - corteza-dev + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: + redis_data: + go_cache: + go_build_cache: + +networks: + corteza-dev: + driver: bridge diff --git a/docker-compose.local-min.yaml b/docker-compose.local-min.yaml new file mode 100644 index 0000000000..ac36a28f05 --- /dev/null +++ b/docker-compose.local-min.yaml @@ -0,0 +1,117 @@ +# docker-compose.local.yaml +# Local development with PostgreSQL and Redis +name: corteza-local +version: "3.8" + +services: + # Corteza Application + corteza: + build: + context: . + dockerfile: Dockerfile.local-min + container_name: corteza-local + env_file: + - server/.env + ports: + - "80:80" + environment: + # Database - PostgreSQL (using DATABASE_URL connection string) + - DB_DSN=postgres://corteza:corteza@postgres:5432/corteza?sslmode=disable + + # Redis + - REDIS_URL=redis://redis:6379 + - QUEUE_URL=redis://redis:6379 + - CACHE_URL=redis://redis:6379 + # Server + - HTTP_ADDR=0.0.0.0:80 + - HTTP_WEBAPP_ENABLED=true + - HTTP_WEBAPP_BASE_DIR=/corteza/webapp + - HTTP_WEBAPP_LIST=admin,compose,workflow,reporter,privacy,discovery,one + - HTTP_SERVER_WEB_CONSOLE_ENABLED=true + + # Domain + - DOMAIN=localhost + - DOMAIN_WEBAPP=localhost + + # Auth - disable for dev + - AUTH_REQUEST_RATE_LIMIT=0 + - AUTH_PASSWORD_SECURITY=false + + # Debug logging + - LOG_DEBUG=true + - LOG_LEVEL=debug + + # Provisioning + - PROVISION_ALWAYS=true + - UPGRADE_ALWAYS=true + + # Disable some features for local dev + - FEDERATION_ENABLED=false + - APIGW_ENABLED=false + + # Required for locale service in development + - ENVIRONMENT=dev + - LOCALE_DEVELOPMENT_MODE=true + volumes: + - corteza-data:/data + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - corteza-local + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/healthcheck"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + # PostgreSQL Database + postgres: + image: postgres:15 + container_name: corteza-local-postgres + environment: + POSTGRES_USER: corteza + POSTGRES_PASSWORD: corteza + POSTGRES_DB: corteza + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - corteza-local + healthcheck: + test: ["CMD-SHELL", "pg_isready -U corteza"] + interval: 5s + timeout: 5s + retries: 5 + + # Redis Cache + redis: + image: redis:7-alpine + container_name: corteza-local-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - corteza-local + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + corteza-data: + driver: local + postgres_data: + driver: local + redis_data: + driver: local + +networks: + corteza-local: + driver: bridge diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml new file mode 100644 index 0000000000..a7d1b48538 --- /dev/null +++ b/docker-compose.local.yaml @@ -0,0 +1,118 @@ +# docker-compose.local.yaml +# Local development with PostgreSQL and Redis +name: corteza-local +version: "3.8" + +services: + # Corteza Application + corteza: + build: + context: . + dockerfile: Dockerfile.local + container_name: corteza-local + env_file: + - server/.env + ports: + - "80:80" + environment: + # Database - PostgreSQL (using DB_DSN connection string) + - DB_DSN=postgres://corteza:corteza@postgres:5432/corteza?sslmode=disable + + # Redis + - REDIS_URL=redis://redis:6379 + - QUEUE_URL=redis://redis:6379 + - CACHE_URL=redis://redis:6379 + + # Server + - HTTP_ADDR=0.0.0.0:80 + - HTTP_WEBAPP_ENABLED=true + - HTTP_WEBAPP_BASE_DIR=/corteza/webapp + - HTTP_WEBAPP_LIST=admin,compose,workflow,reporter,privacy,discovery,one + - HTTP_SERVER_WEB_CONSOLE_ENABLED=true + + # Domain + - DOMAIN=localhost + - DOMAIN_WEBAPP=localhost + + # Auth - disable for dev + - AUTH_REQUEST_RATE_LIMIT=0 + - AUTH_PASSWORD_SECURITY=false + + # Debug logging + - LOG_DEBUG=true + - LOG_LEVEL=debug + + # Provisioning + - PROVISION_ALWAYS=true + - UPGRADE_ALWAYS=true + + # Disable some features for local dev + - FEDERATION_ENABLED=false + - APIGW_ENABLED=false + + # Required for locale service in development + - ENVIRONMENT=dev + - LOCALE_DEVELOPMENT_MODE=true + volumes: + - corteza-data:/data + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - corteza-local + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/healthcheck"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + # PostgreSQL Database + postgres: + image: postgres:15 + container_name: corteza-local-postgres + environment: + POSTGRES_USER: corteza + POSTGRES_PASSWORD: corteza + POSTGRES_DB: corteza + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - corteza-local + healthcheck: + test: ["CMD-SHELL", "pg_isready -U corteza"] + interval: 5s + timeout: 5s + retries: 5 + + # Redis Cache + redis: + image: redis:7-alpine + container_name: corteza-local-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - corteza-local + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + corteza-data: + driver: local + postgres_data: + driver: local + redis_data: + driver: local + +networks: + corteza-local: + driver: bridge diff --git a/lib/js/src/api-clients/compose.ts b/lib/js/src/api-clients/compose.ts index fad9162be1..c12adf1be6 100644 --- a/lib/js/src/api-clients/compose.ts +++ b/lib/js/src/api-clients/compose.ts @@ -228,6 +228,7 @@ export default class Compose { slug, enabled, meta, + blocks, labels, updatedAt, } = (a as KV) || {} @@ -252,6 +253,7 @@ export default class Compose { slug, enabled, meta, + blocks, labels, updatedAt, } diff --git a/lib/js/src/compose/types/namespace.ts b/lib/js/src/compose/types/namespace.ts index eb925b116f..28e0946e70 100644 --- a/lib/js/src/compose/types/namespace.ts +++ b/lib/js/src/compose/types/namespace.ts @@ -1,5 +1,6 @@ import { Apply, CortezaID, ISO8601Date, NoID } from '../../cast' import { IsOf } from '../../guards' +import { PageBlock, PageBlockMaker } from './page-block' interface MetaAdminRecordList { columns: string[]; @@ -22,6 +23,7 @@ interface Meta { interface PartialNamespace extends Partial> { meta?: Partial; + blocks?: Array, createdAt?: string|number|Date; updatedAt?: string|number|Date; deletedAt?: string|number|Date; @@ -38,6 +40,8 @@ export class Namespace { public meta: object = {} + public blocks: Array = [] + public createdAt?: Date = undefined public updatedAt?: Date = undefined public deletedAt?: Date = undefined @@ -78,6 +82,10 @@ export class Namespace { this.labels = { ...n.labels } } + if (n.blocks) { + this.blocks = n.blocks ? n.blocks.filter(b => b.kind).map(block => PageBlockMaker(block)) : [] + } + Apply(this, n, ISO8601Date, 'createdAt', 'updatedAt', 'deletedAt') Apply(this, n, Boolean, 'canDeleteNamespace', diff --git a/lib/js/src/compose/types/page-block/base.ts b/lib/js/src/compose/types/page-block/base.ts index b38c2f837e..3ae9871c77 100644 --- a/lib/js/src/compose/types/page-block/base.ts +++ b/lib/js/src/compose/types/page-block/base.ts @@ -32,6 +32,8 @@ interface PageBlockMeta { customID?: string; customCSSClass?: string; visibility: Visibility; + // `namespaceID` is used to identify what namespace the block belongs too and also if the block is a global block on the namespace + namespaceID?: string; } export type PageBlockInput = PageBlock | Partial @@ -59,6 +61,7 @@ export class PageBlock { expression: '', roles: [], }, + namespaceID: undefined, } public style: PageBlockStyle = { diff --git a/locale/en/corteza-webapp-compose/block.yaml b/locale/en/corteza-webapp-compose/block.yaml index 8ba4c51565..50fb979351 100644 --- a/locale/en/corteza-webapp-compose/block.yaml +++ b/locale/en/corteza-webapp-compose/block.yaml @@ -5,6 +5,8 @@ selector: clone: ref: Clone with references noRef: Clone without references + selectableGlobalBlocks: + placeholder: Select global block from other pages automation: addPlaceholderLabel: Add placeholder (dummy button) availableScriptsAndWorkflow: Available scripts and workflows ({{count}}) @@ -219,6 +221,8 @@ general: tooltip: dragAndDrop: Drag and drop to change order translations: Field translations + globalBlock: + label: Global block iframe: label: IFrame pickURLField: Pick an URL field diff --git a/locale/en/corteza-webapp-compose/page.yaml b/locale/en/corteza-webapp-compose/page.yaml index 4443f5d341..cff2919b31 100644 --- a/locale/en/corteza-webapp-compose/page.yaml +++ b/locale/en/corteza-webapp-compose/page.yaml @@ -79,6 +79,7 @@ block: sample: Sample title: Add new block invalid-handle-characters: Should be at least 2 characters long. Can contain only letters, numbers, underscores and dots. Must end with letter or number + referencedGlobalBlock: This block may be used in other pages changeBlock: Change existing block referencedBlock: This block is used in other layouts copyOf: "Copy of {{title}}" diff --git a/server/Makefile b/server/Makefile index 4eeb57c949..ff11c8d84d 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,10 +1,10 @@ -.PHONY: pack build help qa critic vet codegen provision docs build auth webapp +.PHONY: pack build serve help qa critic vet codegen provision docs build auth webapp include Makefile.inc BUILD_FLAVOUR ?= corteza BUILD_TIME ?= $(shell date +%FT%T%z) -BUILD_VERSION ?= $(shell git describe --tags --abbrev=0) +BUILD_VERSION ?= dev BUILD_ARCH ?= $(shell $(GO) env GOARCH) BUILD_OS ?= $(shell $(GO) env GOOS) BUILD_OS_is_windows = $(filter windows,$(BUILD_OS)) @@ -53,7 +53,7 @@ DEV_MAILHOG_SMTP_ADDR ?= 1025 DEV_MAILHOG_HTTP_ADDR ?= 8025 GIN_ARG_LADDR ?= localhost -GIN_ARGS ?= --laddr $(GIN_ARG_LADDR) --build cmd/corteza --immediate --bin build/gin-bin +GIN_ARGS ?= --laddr $(GIN_ARG_LADDR) --build cmd/corteza --bin build/gin-bin GIN_CORTEZA_ENV_FILE ?= .env GIN_CORTEZA_BIN_ARGS ?= --env-file $(GIN_CORTEZA_ENV_FILE) @@ -83,6 +83,20 @@ build: $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME) $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME): GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) $(GO) build $(LDFLAGS) -o $@ cmd/corteza/main.go +# Run the pre-built server (for development - faster than rebuilding) +# Use this when you've already built and just want to run +run: + cd /workspace/server && DB_DSN='postgres://corteza:corteza@postgres:5432/corteza?sslmode=disable' ENVIRONMENT=dev LOCALE_PATH=/workspace/locale LOCALE_DEVELOPMENT_MODE=true ./$(BUILD_DEST_DIR)/$(BUILD_BIN_NAME) serve + +# Build and run the server (no hot reload - faster for development) +serve: build + cd /workspace/server && DB_DSN='postgres://corteza:corteza@postgres:5432/corteza?sslmode=disable' ENVIRONMENT=dev LOCALE_PATH=/workspace/locale LOCALE_DEVELOPMENT_MODE=true ./$(BUILD_DEST_DIR)/$(BUILD_BIN_NAME) serve + +# Setup locale files for development +# Copies English locale files to pkg/locale/src/en for embedding +locale-setup: + cd /workspace/server/pkg/locale && rm -rf src/en && cp -r /workspace/locale/en ./src + release: build $(BUILD_DEST_DIR)/$(RELEASE_NAME) $(BUILD_DEST_DIR)/$(RELEASE_NAME): diff --git a/server/compose/envoy/store_decode.go b/server/compose/envoy/store_decode.go index 13c5c81e2c..b479e295f0 100644 --- a/server/compose/envoy/store_decode.go +++ b/server/compose/envoy/store_decode.go @@ -164,28 +164,28 @@ func decodePageRefs(p *types.Page) (refs map[string]envoyx.Ref) { for index, b := range p.Blocks { switch b.Kind { case "RecordList": - refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b.Options, index)) case "Automation": - refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b.Options, index)) case "RecordOrganizer": - refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b.Options, index)) case "Chart": - refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b.Options, index)) case "Calendar": - refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b.Options, index)) case "Metric": - refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b.Options, index)) case "Comment": - refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b.Options, index)) case "Progress": - refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b.Options, index)) } } diff --git a/server/compose/envoy/yaml_decode.gen.go b/server/compose/envoy/yaml_decode.gen.go index c368e1d186..d90405777d 100644 --- a/server/compose/envoy/yaml_decode.gen.go +++ b/server/compose/envoy/yaml_decode.gen.go @@ -1306,6 +1306,25 @@ func (d *auxYamlDoc) unmarshalNamespaceNode(dctx documentContext, n *yaml.Node, switch strings.ToLower(k.Value) { + case "blocks": + + // Handle custom node decoder + // + // The decoder may update the passed resource with arbitrary values + // as well as provide additional references and identifiers for the node. + var ( + auxRefs map[string]envoyx.Ref + auxIdents envoyx.Identifiers + ) + auxRefs, auxIdents, err = d.unmarshalNamespaceBlocksNode(r, n) + if err != nil { + return err + } + refs = envoyx.MergeRefs(refs, auxRefs) + ii = ii.Merge(auxIdents) + + break + case "id": // Handle identifiers err = y7s.DecodeScalar(n, "id", &auxNodeValue) diff --git a/server/compose/envoy/yaml_decode.go b/server/compose/envoy/yaml_decode.go index 1135ad5160..e6115d0e62 100644 --- a/server/compose/envoy/yaml_decode.go +++ b/server/compose/envoy/yaml_decode.go @@ -73,40 +73,55 @@ func (d *auxYamlDoc) unmarshalPageBlocksNode(r *types.Page, n *yaml.Node) (refs refs = map[string]envoyx.Ref{} for index, b := range r.Blocks { - switch b.Kind { - case "RecordList": - refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index)) + idents, err = unmarshalResourceBlocksNode(b.Kind, b.Options, index, refs) + } + return +} - case "Automation": - refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index)) +func (d *auxYamlDoc) unmarshalNamespaceBlocksNode(r *types.Namespace, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) { + refs = map[string]envoyx.Ref{} - case "RecordOrganizer": - refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index)) + for index, gBlock := range r.Blocks { + idents, err = unmarshalResourceBlocksNode(gBlock.Kind, gBlock.Options, index, refs) + } - case "Chart": - refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index)) + return +} - case "Calendar": - refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index)) +func unmarshalResourceBlocksNode(kind string, options map[string]interface{}, index int, refs map[string]envoyx.Ref) (idents envoyx.Identifiers, err error) { + switch kind { + case "RecordList": + refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(options, index)) - case "Metric": - refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index)) + case "Automation": + refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(options, index)) - case "Comment": - refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index)) + case "RecordOrganizer": + refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(options, index)) - case "Progress": - refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index)) - } + case "Chart": + refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(options, index)) + + case "Calendar": + refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(options, index)) + + case "Metric": + refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(options, index)) + + case "Comment": + refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(options, index)) + + case "Progress": + refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(options, index)) } return } -func getPageBlockRecordListRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockRecordListRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - id := optString(b.Options, "module", "moduleID") + id := optString(options, "module", "moduleID") if id == "" || id == "0" { return } @@ -119,10 +134,10 @@ func getPageBlockRecordListRefs(b types.PageBlock, index int) (refs map[string]e return } -func getPageBlockChartRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockChartRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - id := optString(b.Options, "chart", "chartID") + id := optString(options, "chart", "chartID") if id == "" || id == "0" { return } @@ -135,10 +150,10 @@ func getPageBlockChartRefs(b types.PageBlock, index int) (refs map[string]envoyx return } -func getPageBlockCalendarRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockCalendarRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - ff, _ := b.Options["feeds"].([]interface{}) + ff, _ := options["feeds"].([]interface{}) for j, f := range ff { feed, _ := f.(map[string]interface{}) opt, _ := (feed["options"]).(map[string]interface{}) @@ -157,10 +172,10 @@ func getPageBlockCalendarRefs(b types.PageBlock, index int) (refs map[string]env return } -func getPageBlockMetricRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockMetricRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - mm, _ := b.Options["metrics"].([]interface{}) + mm, _ := options["metrics"].([]interface{}) for j, m := range mm { mops, _ := m.(map[string]interface{}) @@ -178,26 +193,26 @@ func getPageBlockMetricRefs(b types.PageBlock, index int) (refs map[string]envoy return } -func getPageBlockCommentRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockCommentRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { // Same difference - return getPageBlockRecordListRefs(b, index) + return getPageBlockRecordListRefs(options, index) } -func getPageBlockProgressRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockProgressRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) var aux *envoyx.Ref - aux = getPageBlockProgressValueRefs(b.Options["minValue"]) + aux = getPageBlockProgressValueRefs(options["minValue"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.minValue.ModuleID", index)] = *aux } - aux = getPageBlockProgressValueRefs(b.Options["maxValue"]) + aux = getPageBlockProgressValueRefs(options["maxValue"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.maxValue.ModuleID", index)] = *aux } - aux = getPageBlockProgressValueRefs(b.Options["value"]) + aux = getPageBlockProgressValueRefs(options["value"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.value.ModuleID", index)] = *aux } @@ -226,15 +241,15 @@ func getPageBlockProgressValueRefs(val any) (ref *envoyx.Ref) { } } -func getPageBlockRecordOrganizerRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockRecordOrganizerRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { // Same difference - return getPageBlockRecordListRefs(b, index) + return getPageBlockRecordListRefs(options, index) } -func getPageBlockAutomationRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockAutomationRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - bb, _ := b.Options["buttons"].([]interface{}) + bb, _ := options["buttons"].([]interface{}) for buttonIx, b := range bb { button, _ := b.(map[string]interface{}) id := optString(button, "workflow", "workflowID") @@ -489,4 +504,4 @@ func (d *auxYamlDoc) procMappingRefs(in map[string]string) (out map[string]envoy } return -} +} \ No newline at end of file diff --git a/server/compose/envoy/yaml_encode.gen.go b/server/compose/envoy/yaml_encode.gen.go index 701997a9d3..da343579d6 100644 --- a/server/compose/envoy/yaml_encode.gen.go +++ b/server/compose/envoy/yaml_encode.gen.go @@ -398,6 +398,10 @@ func (e YamlEncoder) encodeNamespace(ctx context.Context, p envoyx.EncodeParams, res := node.Resource.(*types.Namespace) // Pre-compute some map values so we can omit error checking when encoding yaml nodes + auxBlocks, err := e.encodeNamespaceBlocksC(ctx, p, tt, node, res, res.Blocks) + if err != nil { + return + } auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt) if err != nil { return @@ -413,6 +417,7 @@ func (e YamlEncoder) encodeNamespace(ctx context.Context, p envoyx.EncodeParams, } out, err = y7s.AddMap(out, + "blocks", auxBlocks, "createdAt", auxCreatedAt, "deletedAt", auxDeletedAt, "enabled", res.Enabled, @@ -718,4 +723,4 @@ func safeParentIdentifier(tt envoyx.Traverser, n *envoyx.Node, ref envoyx.Ref) ( } return aux.Identifiers.FriendlyIdentifier() -} +} \ No newline at end of file diff --git a/server/compose/envoy/yaml_encode.go b/server/compose/envoy/yaml_encode.go index 6a5dc8420d..624abc5f9a 100644 --- a/server/compose/envoy/yaml_encode.go +++ b/server/compose/envoy/yaml_encode.go @@ -1,14 +1,13 @@ package envoy import ( - "context" - "fmt" - - "github.com/cortezaproject/corteza/server/compose/types" - "github.com/cortezaproject/corteza/server/pkg/envoyx" - "github.com/cortezaproject/corteza/server/pkg/y7s" - "github.com/modern-go/reflect2" - "gopkg.in/yaml.v3" + "context" + "fmt" + "github.com/cortezaproject/corteza/server/compose/types" + "github.com/cortezaproject/corteza/server/pkg/envoyx" + "github.com/cortezaproject/corteza/server/pkg/y7s" + "github.com/modern-go/reflect2" + "gopkg.in/yaml.v3" ) func (e YamlEncoder) encode(ctx context.Context, base *yaml.Node, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) { @@ -89,128 +88,152 @@ func (e YamlEncoder) encodeModuleFieldOptionsC(ctx context.Context, p envoyx.Enc } func (e YamlEncoder) encodePageBlocksC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, bb types.PageBlocks) (_ any, err error) { + var aux any out, _ := y7s.MakeSeq() - var aux any for i, b := range pg.Blocks { - aux, err = e.encodePageBlockC(ctx, p, tt, n, pg, i, b) + options, err := e.encodePageBlockC(ctx, p, tt, n, i, b.Kind, b.Options) if err != nil { - return + return nil, err } + b.Options = options + aux = b + out, err = y7s.AddSeq(out, aux) if err != nil { - return + return nil, err } } return out, nil } -func (e YamlEncoder) encodePageBlockC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, index int, b types.PageBlock) (_ any, err error) { - - switch b.Kind { - case "RecordList": - b = e.cleanupPageblockRecordList(b) - - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break - - case "RecordOrganizer": - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break - - case "Chart": - chrRef := n.References[fmt.Sprintf("Blocks.%d.Options.ChartID", index)] - b.Options["chart"] = safeParentIdentifier(tt, n, chrRef) - delete(b.Options, "chartID") - break - - case "Calendar": - ff, _ := b.Options["feeds"].([]interface{}) - for i, f := range ff { - feed, _ := f.(map[string]interface{}) - fOpts, _ := (feed["options"]).(map[string]interface{}) - - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, i)] - fOpts["module"] = safeParentIdentifier(tt, n, modRef) - delete(fOpts, "moduleID") - } - break - - case "Automation": - bb, _ := b.Options["buttons"].([]interface{}) - for i, b := range bb { - button, _ := b.(map[string]interface{}) - if _, has := button["workflowID"]; !has { - continue - } - - wfRef := n.References[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, i)] - button["workflow"] = safeParentIdentifier(tt, n, wfRef) - delete(button, "workflowID") - i++ - } - break - - case "Metric": - mm, _ := b.Options["metrics"].([]interface{}) - for i, m := range mm { - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, i)] +func (e YamlEncoder) encodeNamespaceBlocksC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, ns *types.Namespace, bb types.GlobalBlocks) (_ any, err error) { + var aux any + out, _ := y7s.MakeSeq() - mops, _ := m.(map[string]interface{}) - mops["module"] = safeParentIdentifier(tt, n, modRef) - delete(mops, "moduleID") - } - break + for i, gB := range ns.Blocks { + options, err := e.encodePageBlockC(ctx, p, tt, n, i, gB.Kind, gB.Options) + if err != nil { + return nil, err + } - case "Comment": - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break + gB.Options = options + aux = gB - case "Progress": - err = e.encodeProgressPageblockVal("minValue", index, n, tt, &b) - if err != nil { - return - } + out, err = y7s.AddSeq(out, aux) + if err != nil { + return nil, err + } + } - err = e.encodeProgressPageblockVal("maxValue", index, n, tt, &b) - if err != nil { - return - } - - err = e.encodeProgressPageblockVal("value", index, n, tt, &b) - if err != nil { - return - } - break - } + return out, nil +} - return b, nil +func (e YamlEncoder) encodePageBlockC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, index int, kind string, options map[string]interface{}) (opts map[string]interface{}, err error) { + switch kind { + case "RecordList": + options = e.cleanupPageblockRecordList(options) + + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "RecordOrganizer": + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "Chart": + chrRef := n.References[fmt.Sprintf("Blocks.%d.Options.ChartID", index)] + options["chart"] = safeParentIdentifier(tt, n, chrRef) + delete(options, "chartID") + break + + case "Calendar": + ff, _ := options["feeds"].([]interface{}) + for i, f := range ff { + feed, _ := f.(map[string]interface{}) + fOpts, _ := (feed["options"]).(map[string]interface{}) + + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, i)] + fOpts["module"] = safeParentIdentifier(tt, n, modRef) + delete(fOpts, "moduleID") + } + break + + case "Automation": + bb, _ := options["buttons"].([]interface{}) + for i, b := range bb { + button, _ := b.(map[string]interface{}) + if _, has := button["workflowID"]; !has { + continue + } + + wfRef := n.References[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, i)] + button["workflow"] = safeParentIdentifier(tt, n, wfRef) + delete(button, "workflowID") + i++ + } + break + + case "Metric": + mm, _ := options["metrics"].([]interface{}) + for i, m := range mm { + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, i)] + + mops, _ := m.(map[string]interface{}) + mops["module"] = safeParentIdentifier(tt, n, modRef) + delete(mops, "moduleID") + } + break + + case "Comment": + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "Progress": + options, err = e.encodeProgressPageblockVal("minValue", index, n, tt, options) + if err != nil { + return + } + + options, err = e.encodeProgressPageblockVal("maxValue", index, n, tt, options) + if err != nil { + return + } + + options, err = e.encodeProgressPageblockVal("value", index, n, tt, options) + if err != nil { + return + } + break + } + + return options, nil } -func (e YamlEncoder) encodeProgressPageblockVal(k string, index int, n *envoyx.Node, tt envoyx.Traverser, b *types.PageBlock) (err error) { - if reflect2.IsNil(b.Options[k]) { +func (e YamlEncoder) encodeProgressPageblockVal(k string, index int, n *envoyx.Node, tt envoyx.Traverser, options map[string]interface{}) (opts map[string]interface{}, err error) { + if reflect2.IsNil(options[k]) { return } - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.%s.ModuleID", index, k)] - opt := b.Options[k].(map[string]any) - opt["moduleID"] = safeParentIdentifier(tt, n, modRef) - delete(opt, "moduleID") + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.%s.ModuleID", index, k)] + opt := options[k].(map[string]any) + opt["moduleID"] = safeParentIdentifier(tt, n, modRef) + delete(opt, "moduleID") - return + return } -func (e YamlEncoder) cleanupPageblockRecordList(b types.PageBlock) (out types.PageBlock) { - out = b - rawFF, has := out.Options["fields"] +func (e YamlEncoder) cleanupPageblockRecordList(options map[string]interface{}) (out map[string]interface{}) { + out = options + rawFF, has := out["fields"] if !has { return } @@ -232,7 +255,7 @@ func (e YamlEncoder) cleanupPageblockRecordList(b types.PageBlock) (out types.Pa } } - out.Options["fields"] = retFF + out["fields"] = retFF - return b -} + return +} \ No newline at end of file diff --git a/server/compose/model/models.gen.go b/server/compose/model/models.gen.go index 13f1992f59..00f4082360 100644 --- a/server/compose/model/models.gen.go +++ b/server/compose/model/models.gen.go @@ -522,6 +522,15 @@ var Namespace = &dal.Model{ Store: &dal.CodecAlias{Ident: "meta"}, }, + &dal.Attribute{ + Ident: "Blocks", + Type: &dal.TypeJSON{ + HasDefault: true, + DefaultValue: "[]", + }, + Store: &dal.CodecAlias{Ident: "blocks"}, + }, + &dal.Attribute{ Ident: "Name", Sortable: true, Type: &dal.TypeText{}, @@ -1200,4 +1209,4 @@ func init() { Record, RecordRevision, ) -} +} \ No newline at end of file diff --git a/server/compose/namespace.cue b/server/compose/namespace.cue index dcfba7d91f..d767c64005 100644 --- a/server/compose/namespace.cue +++ b/server/compose/namespace.cue @@ -27,6 +27,18 @@ namespace: { omitSetter: true omitGetter: true } + blocks: { + goType: "types.GlobalBlocks" + dal: { type: "JSON", defaultEmptyObject: true } + omitSetter: true + omitGetter: true + envoy: { + yaml: { + customDecoder: true + customEncoder: true + } + } + } name: { sortable: true dal: {} diff --git a/server/compose/rest.yaml b/server/compose/rest.yaml index 271805e7bc..78e2006c86 100644 --- a/server/compose/rest.yaml +++ b/server/compose/rest.yaml @@ -73,6 +73,10 @@ endpoints: name: meta required: true title: Meta data + - type: sqlxTypes.JSONText + name: blocks + required: false + title: Blocks - name: read method: GET title: Read namespace diff --git a/server/compose/rest/namespace.go b/server/compose/rest/namespace.go index c399fc5f6a..1b01e43833 100644 --- a/server/compose/rest/namespace.go +++ b/server/compose/rest/namespace.go @@ -140,6 +140,14 @@ func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (i return nil, err } + if len(r.Blocks) > 2 { + // Process blocks if they were included in the request + // if not, do not assume that blocks were removed! + if err = r.Blocks.Unmarshal(&ns.Blocks); err != nil { + return nil, err + } + } + ns, err = ctrl.namespace.Create(ctx, ns) return ctrl.makePayload(ctx, ns, err) } @@ -174,6 +182,14 @@ func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (i return nil, err } + if len(r.Blocks) > 2 { + // Process blocks if they were included in the request + // if not, do not assume that blocks were removed! + if err = r.Blocks.Unmarshal(&ns.Blocks); err != nil { + return nil, err + } + } + ns, err = ctrl.namespace.Update(ctx, ns) return ctrl.makePayload(ctx, ns, err) } diff --git a/server/compose/rest/request/namespace.go b/server/compose/rest/request/namespace.go index ec7d030ca0..43586e2ad0 100644 --- a/server/compose/rest/request/namespace.go +++ b/server/compose/rest/request/namespace.go @@ -100,6 +100,11 @@ type ( // // Meta data Meta sqlxTypes.JSONText + + // Blocks POST parameter + // + // Blocks + Blocks sqlxTypes.JSONText } NamespaceRead struct { @@ -135,6 +140,11 @@ type ( // Meta data Meta sqlxTypes.JSONText + // Blocks POST parameter + // + // Blocks + Blocks sqlxTypes.JSONText + // Labels POST parameter // // Labels @@ -380,6 +390,7 @@ func (r NamespaceCreate) Auditable() map[string]interface{} { "slug": r.Slug, "enabled": r.Enabled, "meta": r.Meta, + "blocks": r.Blocks, } } @@ -408,6 +419,11 @@ func (r NamespaceCreate) GetMeta() sqlxTypes.JSONText { return r.Meta } +// Auditable returns all auditable/loggable parameters +func (r NamespaceCreate) GetBlocks() sqlxTypes.JSONText { + return r.Blocks +} + // Fill processes request and fills internal variables func (r *NamespaceCreate) Fill(req *http.Request) (err error) { @@ -468,6 +484,13 @@ func (r *NamespaceCreate) Fill(req *http.Request) (err error) { return err } } + + if val, ok := req.MultipartForm.Value["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } } } @@ -517,6 +540,13 @@ func (r *NamespaceCreate) Fill(req *http.Request) (err error) { return err } } + + if val, ok := req.Form["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } } return err @@ -570,6 +600,7 @@ func (r NamespaceUpdate) Auditable() map[string]interface{} { "slug": r.Slug, "enabled": r.Enabled, "meta": r.Meta, + "blocks": r.Blocks, "labels": r.Labels, "updatedAt": r.UpdatedAt, } @@ -600,6 +631,11 @@ func (r NamespaceUpdate) GetMeta() sqlxTypes.JSONText { return r.Meta } +// Auditable returns all auditable/loggable parameters +func (r NamespaceUpdate) GetBlocks() sqlxTypes.JSONText { + return r.Blocks +} + // Auditable returns all auditable/loggable parameters func (r NamespaceUpdate) GetLabels() map[string]labelTypes.LabelValue { return r.Labels @@ -659,6 +695,13 @@ func (r *NamespaceUpdate) Fill(req *http.Request) (err error) { } } + if val, ok := req.MultipartForm.Value["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } + if val, ok := req.MultipartForm.Value["labels[]"]; ok { r.Labels, err = label.ParseStrings(val) if err != nil { @@ -715,6 +758,13 @@ func (r *NamespaceUpdate) Fill(req *http.Request) (err error) { } } + if val, ok := req.Form["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } + if val, ok := req.Form["labels[]"]; ok { r.Labels, err = label.ParseStrings(val) if err != nil { diff --git a/server/compose/service/namespace.go b/server/compose/service/namespace.go index 38a8476af3..96a8bb2a92 100644 --- a/server/compose/service/namespace.go +++ b/server/compose/service/namespace.go @@ -653,6 +653,37 @@ func (svc namespace) handleUpdate(ctx context.Context, upd *types.Namespace) nam res.Meta = upd.Meta } + // Get max blockID for later use + blockID := uint64(0) + for _, b := range res.Blocks { + if b.BlockID == blockID { + // assign ids on new page blocks + for i := range upd.Blocks { + upd.Blocks[i].BlockID = uint64(i) + 1 + } + } + + if b.BlockID > blockID { + blockID = b.BlockID + } + } + + if !reflect.DeepEqual(res.Blocks, upd.Blocks) { + res.Blocks = upd.Blocks + changes |= namespaceChanged + } + + // Assure blockIDs + for i, b := range res.Blocks { + if b.BlockID == 0 { + blockID++ + b.BlockID = blockID + res.Blocks[i] = b + + changes |= namespaceChanged + } + } + if upd.Labels != nil { if label.Changed(res.Labels, upd.Labels) { changes |= namespaceLabelsChanged diff --git a/server/compose/types/namespace.go b/server/compose/types/namespace.go index ea181286eb..509fd2e65a 100644 --- a/server/compose/types/namespace.go +++ b/server/compose/types/namespace.go @@ -16,6 +16,7 @@ type ( Slug string `json:"slug"` Enabled bool `json:"enabled"` Meta NamespaceMeta `json:"meta"` + Blocks GlobalBlocks `json:"blocks"` Labels map[string]labelTypes.LabelValue `json:"labels,omitempty"` @@ -29,6 +30,28 @@ type ( Name string `json:"name"` } + GlobalBlocks []GlobalBlock + + GlobalBlock struct { + BlockID uint64 `json:"blockID,string,omitempty"` + + Options map[string]interface{} `json:"options,omitempty"` + Style PageBlockStyle `json:"style,omitempty"` + Kind string `json:"kind"` + XYWH [4]int `json:"xywh"` // x,y,w,h + Meta map[string]any `json:"meta,omitempty"` + + // Warning: value of this field is now handled via resource-translation facility + // struct field is kept for the convenience for now since it allows us + // easy encoding/decoding of the outgoing/incoming values + Title string `json:"title,omitempty"` + + // Warning: value of this field is now handled via resource-translation facility + // struct field is kept for the convenience for now since it allows us + // easy encoding/decoding of the outgoing/incoming values + Description string `json:"description,omitempty"` + } + NamespaceFilter struct { NamespaceID []string `json:"namespaceID"` @@ -92,3 +115,11 @@ func (set NamespaceSet) FindByHandle(handle string) *Namespace { func (nm *NamespaceMeta) Scan(src any) error { return sql.ParseJSON(src, nm) } func (nm NamespaceMeta) Value() (driver.Value, error) { return json.Marshal(nm) } + +func (nm *GlobalBlocks) Scan(src any) error { return sql.ParseJSON(src, nm) } +func (nm GlobalBlocks) Value() (driver.Value, error) { + if nm == nil { + return "[]", nil + } + return json.Marshal(nm) +} diff --git a/server/store/adapters/rdbms/aux_types.gen.go b/server/store/adapters/rdbms/aux_types.gen.go index 96a72d66df..4da509c55c 100644 --- a/server/store/adapters/rdbms/aux_types.gen.go +++ b/server/store/adapters/rdbms/aux_types.gen.go @@ -279,6 +279,7 @@ type ( Slug string `db:"slug"` Enabled bool `db:"enabled"` Meta composeType.NamespaceMeta `db:"meta"` + Blocks composeType.GlobalBlocks `db:"blocks"` Name string `db:"name"` CreatedAt time.Time `db:"created_at"` UpdatedAt *time.Time `db:"updated_at"` @@ -1619,6 +1620,7 @@ func (aux *auxComposeNamespace) encode(res *composeType.Namespace) (_ error) { aux.Slug = res.Slug aux.Enabled = res.Enabled aux.Meta = res.Meta + aux.Blocks = res.Blocks aux.Name = res.Name aux.CreatedAt = res.CreatedAt aux.UpdatedAt = res.UpdatedAt @@ -1635,6 +1637,7 @@ func (aux auxComposeNamespace) decode() (res *composeType.Namespace, _ error) { res.Slug = aux.Slug res.Enabled = aux.Enabled res.Meta = aux.Meta + res.Blocks = aux.Blocks res.Name = aux.Name res.CreatedAt = aux.CreatedAt res.UpdatedAt = aux.UpdatedAt @@ -1651,6 +1654,7 @@ func (aux *auxComposeNamespace) scan(row scanner) error { &aux.Slug, &aux.Enabled, &aux.Meta, + &aux.Blocks, &aux.Name, &aux.CreatedAt, &aux.UpdatedAt, diff --git a/server/store/adapters/rdbms/queries.gen.go b/server/store/adapters/rdbms/queries.gen.go index 78e231e6d0..f4840a28cb 100644 --- a/server/store/adapters/rdbms/queries.gen.go +++ b/server/store/adapters/rdbms/queries.gen.go @@ -1869,6 +1869,7 @@ var ( "slug", "enabled", "meta", + "blocks", "name", "created_at", "updated_at", @@ -1886,6 +1887,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, @@ -1906,6 +1908,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, @@ -1924,6 +1927,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, diff --git a/server/store/adapters/rdbms/upgrade_fixes.go b/server/store/adapters/rdbms/upgrade_fixes.go index 4ea62756b4..85d55ce662 100644 --- a/server/store/adapters/rdbms/upgrade_fixes.go +++ b/server/store/adapters/rdbms/upgrade_fixes.go @@ -55,6 +55,7 @@ var ( fix_2024_09_03_renameFederationNodeSyncComposeID, fix_2024_09_03_addFederationNodeSyncNodeIDIndex, fix_2024_9_7_migrateLabelsValueToJsonb, + fix_2024_09_07_migrateNamespacePageBlocks, } fixesPost = []func(context.Context, *Store) error{ @@ -1312,6 +1313,14 @@ func fix_2024_9_7_migrateLabelsValueToJsonb(ctx context.Context, s *Store) (err return nil } + +func fix_2024_09_07_migrateNamespacePageBlocks(ctx context.Context, s *Store) (err error) { + return addColumn(ctx, s, + "compose_namespace", + &dal.Attribute{Ident: "blocks", Type: &dal.TypeJSON{HasDefault: true, DefaultValue: "[]"}}, + ) +} + func count(ctx context.Context, s *Store, table string, ee ...goqu.Expression) (count int) { db := s.DB.(goqu.SQLDatabase) diff --git a/server/webapp/Makefile b/server/webapp/Makefile index 9cd4554dd9..bdd5a9a185 100644 --- a/server/webapp/Makefile +++ b/server/webapp/Makefile @@ -1,4 +1,4 @@ -.PHONY: all install-fresh install-packages build clean cleaner +.PHONY: all install-fresh install-packages build clean cleaner deps WGET ?= wget WGET_FLAGS ?= -q @@ -9,7 +9,7 @@ APPS ?= admin compose workflow reporter discovery privacy ALL_APPS ?= $(APPS) one VERSION ?= $(shell git describe --tags --abbrev=0) PACKAGES = $(addprefix corteza-webapp-,$(addsuffix -$(VERSION).tar.gz,$(ALL_APPS))) -SOURCE_LOC ?= "../../webapp-" +SOURCE_LOC ?= ../../client/ SOURCES = $(addprefix $(SOURCE_LOC),$(ALL_APPS)) all: install-packages @@ -32,21 +32,21 @@ $(PACKAGES): $(info downloading $(RELEASE_PAGE)/$(@)) @ $(WGET) $(WGET_FLAGS) $(RELEASE_PAGE)/$(@) -install-fresh: build +install-fresh: #build @ rm -rf public - @ cp -r $(SOURCE_LOC)one/dist public/ - @ cp -r $(SOURCE_LOC)admin/dist public/admin - @ cp -r $(SOURCE_LOC)compose/dist public/compose - @ cp -r $(SOURCE_LOC)workflow/dist public/workflow - @ cp -r $(SOURCE_LOC)reporter/dist public/reporter - @ cp -r $(SOURCE_LOC)discovery/dist public/discovery - @ cp -r $(SOURCE_LOC)privacy/dist public/privacy - -build: $(SOURCES) - -$(SOURCES): - $(info building $(@)) - @ cd $(@) && yarn build + @ cp -r $(SOURCE_LOC)web/one/dist public/ + @ cp -r $(SOURCE_LOC)web/admin/dist public/admin + @ cp -r $(SOURCE_LOC)web/compose/dist public/compose + @ cp -r $(SOURCE_LOC)web/workflow/dist public/workflow + @ cp -r $(SOURCE_LOC)web/reporter/dist public/reporter + @ cp -r $(SOURCE_LOC)web/discovery/dist public/discovery + @ cp -r $(SOURCE_LOC)web/privacy/dist public/privacy + +build: + $(info building libs first) + @ cd ../../lib && make dev + $(info building client apps) + @ cd $(SOURCE_LOC) && make build clean: @ rm -f $(PACKAGES)