Skip to content
Commits on Source (236)
...@@ -6,6 +6,7 @@ packages/build ...@@ -6,6 +6,7 @@ packages/build
Dockerfile Dockerfile
# dotfiles # dotfiles
.git* .gitignore
.vscode .vscode
**/.env* **/.env*
\ No newline at end of file .gitlab-ci.yml
\ No newline at end of file
{
"root": true,
"ignorePatterns": ["node_modules", "packages/**/dist"],
"plugins": ["simple-import-sort"],
"rules": {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
}
}
stages:
- lint
- build
- test
- deploy
eslint client:
stage: lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- packages/client/src/**/*
image: node:23-alpine
script:
- npm i --include=dev
- npm -w packages/client run lint
artifacts:
reports:
codequality: packages/client/gl-codequality.json
eslint server:
stage: lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- packages/server/src/**/*
image: node:23-alpine
script:
- npm i --include=dev
- npm -w packages/server run lint
artifacts:
reports:
codequality: packages/server/gl-codequality.json
build wiki:
stage: build
trigger:
include: .gitlab/ci/wiki.yml
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy:
stage: deploy
trigger:
include: .gitlab/ci/deploy.yml
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
REGISTRY: registry.sc07.dev
IMAGE_NAME: sc07/canvas:edge
publish:
stage: build
tags:
- red
interruptible: true
before_script:
- echo $PAT | docker login $REGISTRY -u $GITLAB_USER_LOGIN --password-stdin
script:
- |
docker build --tag $REGISTRY/$IMAGE_NAME \
--build-arg SENTRY_URL=$SENTRY_URL \
--build-arg SENTRY_ORG=$SENTRY_ORG \
--build-arg CLIENT_SENTRY_PROJECT=$CLIENT_SENTRY_PROJECT \
--build-arg CLIENT_SENTRY_DSN=$CLIENT_SENTRY_DSN \
--build-arg SERVER_SENTRY_PROJECT=$SERVER_SENTRY_PROJECT \
--build-arg SERVER_SENTRY_DSN=$SERVER_SENTRY_DSN \
--build-arg SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN .
- docker push $REGISTRY/$IMAGE_NAME
deploy:
stage: deploy
tags:
- red
interruptible: true
script:
- cd $WORK_DIR
- docker compose pull
- docker compose up -d
# sync /doc/ to internal wiki repo for UI access
# see #151
build-wiki:
image: alpine
stage: build
before_script:
- apk add --no-cache git git-subtree
script:
- git config user.email "ci@sc07.company"
- git config user.name "ci"
- git remote remove gitlab-wiki || true
- git remote add gitlab-wiki "https://ci:$CI_TOKEN@sc07.dev/sc07/canvas.wiki.git"
- git status
- git checkout main
- git pull
- git push gitlab-wiki `git subtree split -P doc main`:main --force
version: 2
update-options:
rebase-strategy: all
reviewers:
- grant
vulnerability-alerts:
enabled: true
confidential: true
assignees:
- grant
updates:
- package-ecosystem: "npm"
directories:
- "/"
- "/packages/admin"
- "/packages/client"
- "/packages/lib"
- "/packages/server"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
v23.5.0
FROM node:20-alpine AS base FROM node:23-alpine AS base
# to be able to read git hash
RUN apk -U upgrade && apk add --no-cache git openssh
RUN git config --global --add safe.directory /home/node/app
FROM base as dev_dep
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
# --- dependencies ---
COPY --chown=node:node package*.json ./
COPY --chown=node:node packages/admin/package*.json ./packages/admin/
COPY --chown=node:node packages/client/package*.json ./packages/client/
COPY --chown=node:node packages/lib/package*.json ./packages/lib/
COPY --chown=node:node packages/server/package*.json ./packages/server/
USER node
RUN npm install --include=dev
FROM base as dep
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
# --- dependencies ---
COPY --chown=node:node package*.json ./
COPY --chown=node:node packages/admin/package*.json ./packages/admin/
COPY --chown=node:node packages/client/package*.json ./packages/client/
COPY --chown=node:node packages/lib/package*.json ./packages/lib/
COPY --chown=node:node packages/server/package*.json ./packages/server/
USER node
RUN npm install --omit=dev
# #
# === BUILDER === # === BUILDER ===
...@@ -7,10 +39,20 @@ FROM node:20-alpine AS base ...@@ -7,10 +39,20 @@ FROM node:20-alpine AS base
FROM base as build FROM base as build
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app WORKDIR /home/node/app
COPY --chown=node:node . .
USER node # CLIENT_SENTRY_DSN will be baked into the client package
RUN npm install --include=dev # SERVER_SENTRY_DSN is NOT baked in and must also be supplied to image on start
# SENTRY_{URL,ORG,PROJECT,AUTH_TOKEN} are used for sourcemap pushing
ARG CLIENT_SENTRY_DSN
ARG CLIENT_SENTRY_PROJECT
ARG SERVER_SENTRY_DSN
ARG SERVER_SENTRY_PROJECT
ARG SENTRY_URL
ARG SENTRY_ORG
ARG SENTRY_AUTH_TOKEN
COPY --from=dev_dep --chown=node:node /home/node/app/ ./
COPY --chown=node:node . .
# --- build lib --- # --- build lib ---
...@@ -20,16 +62,23 @@ RUN sed -i -e 's/"main": ".*"/"main": ".\/dist\/index.js"/' packages/lib/package ...@@ -20,16 +62,23 @@ RUN sed -i -e 's/"main": ".*"/"main": ".\/dist\/index.js"/' packages/lib/package
# --- build main client --- # --- build main client ---
ARG VITE_INCLUDE_EVENT_INFO
ARG SENTRY_DSN=$CLIENT_SENTRY_DSN
ARG SENTRY_PROJECT=$CLIENT_SENTRY_PROJECT
RUN npm -w packages/client run build RUN npm -w packages/client run build
# --- build admin --- # --- build admin ---
ENV APP_ROOT /admin
RUN npm -w packages/admin run build RUN npm -w packages/admin run build
# --- build server --- # --- build server ---
RUN npx -w packages/server prisma generate RUN npx -w packages/server prisma generate
ARG SENTRY_DSN=$SERVER_SENTRY_DSN
ARG SENTRY_PROJECT=$SERVER_SENTRY_PROJECT
RUN npm -w packages/server run build RUN npm -w packages/server run build
RUN if [ -n "$SENTRY_AUTH_TOKEN" ]; then npm -w packages/server run sentry; fi
# #
# === RUNNER === # === RUNNER ===
...@@ -37,7 +86,8 @@ RUN npm -w packages/server run build ...@@ -37,7 +86,8 @@ RUN npm -w packages/server run build
FROM base as run FROM base as run
WORKDIR /home/node/app WORKDIR /home/node/app
COPY package*.json docker-start.sh ./ COPY --from=dep /home/node/app/ ./
COPY package*.json docker-start*.sh .git ./
# --- prepare lib --- # --- prepare lib ---
...@@ -65,7 +115,6 @@ COPY --from=build /home/node/app/packages/server/dist ./packages/server/dist ...@@ -65,7 +115,6 @@ COPY --from=build /home/node/app/packages/server/dist ./packages/server/dist
# --- finalize --- # --- finalize ---
RUN npm install --omit=dev
RUN npx -w packages/server prisma generate RUN npx -w packages/server prisma generate
# set runtime env variables # set runtime env variables
...@@ -74,7 +123,13 @@ ENV PORT 3000 ...@@ -74,7 +123,13 @@ ENV PORT 3000
ENV NODE_ENV production ENV NODE_ENV production
ENV SERVE_CLIENT /home/node/app/packages/client ENV SERVE_CLIENT /home/node/app/packages/client
ENV SERVE_ADMIN /home/node/app/packages/admin ENV SERVE_ADMIN /home/node/app/packages/admin
ENV PIXEL_LOG_PATH /home/node/app/pixel.log
VOLUME /home/node/app/pixel.log
EXPOSE 3000 EXPOSE 3000
# profiler port, only used if profiler is explicity running
EXPOSE 9229
ENTRYPOINT [ "/bin/sh" ] ENTRYPOINT [ "/bin/sh" ]
CMD [ "./docker-start.sh" ] CMD [ "./docker-start.sh" ]
\ No newline at end of file
---
title: Sentry
---
The frontend & backend are both equiped with [Sentry](https://sentry.io) error reporting to aid with debugging.
You can selfhost Sentry [using this guide](https://develop.sentry.dev/self-hosted/).
**Note:** if the Sentry DSN environment variable isn't present, Sentry will not be initialized and therefore will not send any information
**Note:** if the PROJECT, ORG, URL, AUTH_TOKEN is not provided sourcemaps will not be uploaded at build time
**Environment Variable Keys:**
- `build` used during the build phase (usually `npm run build`)
- `dev` used during development (usually `npm run dev`)
- `runtime` used during runtime/deployment
# Docker Building
Docker builds will take the build environment variables as `--build-arg`s, with a couple changes
- Frontend's `SENTRY_DSN` & `SENTRY_PROJECT` are renamed to `CLIENT_SENTRY_DSN` & `CLIENT_SENTRY_PROJECT` respectfully
- Backend's `SENTRY_DSN` & `SENTRY_PROJECT` are renamed to `SERVER_SENTRY_DSN` & `SERVER_SENTRY_PROJECT` respectfully
As each project has to be separated within Sentry, the DSN & project IDs will be different
_See `/Dockerfile` for how they're used_<br />
_See `/.gitlab/ci/deploy.yml` for an example of a `docker build` with the arguments_
# Frontend
With how Vite builds, the environment variables will be used during the build stage to generate the client
## Environment
| Environment | Uses | Description |
| ------------------- | ---------- | ---------------------------------------------------------------------------------------------- |
| `SENTRY_DSN` | build, dev | Frontend DSN, baked into the client as `__SENTRY_DSN__` (see `packages/client/vite.config.js`) |
| `SENTRY_PROJECT` | build | Project ID, used to send sourcemaps to Sentry |
| `SENTRY_ORG` | build | Project organization ID, used to send sourcemaps |
| `SENTRY_URL` | build | Sentry hostname (eg `https://sentry.io`) |
| `SENTRY_AUTH_TOKEN` | build | Auth token for sending the sourcemaps |
# Backend
## Environment
| Environment | Uses | Description |
| --------------------------- | ------------------- | --------------------------------------------------------------- |
| `SENTRY_DSN` | build, runtime, dev | Backend DSN |
| `SENTRY_TUNNEL_PROJECT_IDS` | runtime, dev | Comma-separated list of project IDs to allow through the tunnel |
| `SENTRY_PROJECT` | build | Project ID, used to send sourcemaps |
| `SENTRY_ORG` | build | Project organization ID, used to send sourcemaps |
| `SENTRY_URL` | build | Sentry hostname (eg `https://sentry.io`) |
| `SENTRY_AUTH_TOKEN` | build | Auth token for sending sourcemaps |
| `SENTRY_ENVIRONMENT` | runtime | Passed to Sentry to log what environment we're running in |
# Canvas
Documentation example
...@@ -9,16 +9,29 @@ services: ...@@ -9,16 +9,29 @@ services:
build: . build: .
ports: ports:
- "3000:3000" - "3000:3000"
# - "9229:9229"
environment: environment:
- SESSION_SECRET=CHANGE ME TO RANDOM VALUE
- REDIS_HOST=redis://redis - REDIS_HOST=redis://redis
- DATABASE_URL=postgres://postgres@postgres/canvas - DATABASE_URL=postgres://postgres@postgres/canvas
- AUTH_ENDPOINT=https://auth.fediverse.events # - SCRIPT_TO_RUN=profiler
- AUTH_CLIENT=canvas env_file:
- AUTH_SECRET=${AUTH_SECRET} - .env.local
depends_on: depends_on:
- redis redis:
- postgres condition: service_healthy
postgres:
condition: service_healthy
worker:
image: sc07/canvas
build: .
environment:
- REDIS_HOST=redis://redis
- DATABASE_URL=postgres://postgres@postgres/canvas
env_file:
- .env.local
depends_on:
- canvas
command: ./docker-start-worker.sh
redis: redis:
restart: always restart: always
image: redis:7-alpine image: redis:7-alpine
......
#!/bin/sh
npm -w packages/server run tool start_job_worker
\ No newline at end of file
...@@ -3,5 +3,8 @@ ...@@ -3,5 +3,8 @@
# This script runs when the docker image starts # This script runs when the docker image starts
# It just forces all migrations to run and then starts lol # It just forces all migrations to run and then starts lol
# Allow running of other scripts via environment variable (eg. profiler)
SCRIPT_TO_RUN="${SCRIPT_TO_RUN:-start}"
npx -w packages/server npx prisma migrate deploy npx -w packages/server npx prisma migrate deploy
npm -w packages/server run start npm -w packages/server run $SCRIPT_TO_RUN
\ No newline at end of file \ No newline at end of file
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
], ],
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
"lint": "eslint",
"dev:client": "npm run dev -w packages/client", "dev:client": "npm run dev -w packages/client",
"dev:server": "npm run dev -w packages/server", "dev:server": "npm run dev -w packages/server",
"prisma:studio": "npm run prisma:studio -w packages/server", "prisma:studio": "npm run prisma:studio -w packages/server",
...@@ -24,14 +25,41 @@ ...@@ -24,14 +25,41 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@tsconfig/recommended": "^1.0.2", "@sentry/cli": "^2.40.0",
"dotenv": "^16.3.1", "@sentry/vite-plugin": "^2.22.7",
"nodemon": "^3.0.1", "@tsconfig/recommended": "^1.0.8",
"prettier": "^3.0.1", "@tsconfig/vite-react": "^3.4.0",
"@types/react": "^19.0.3",
"@types/react-dom": "^19.0.2",
"@typescript-eslint/eslint-plugin": "^8.19.1",
"@typescript-eslint/parser": "^8.19.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"dotenv": "^16.4.7",
"dotenv-cli": "^8.0.0",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-formatter-gitlab": "^5.1.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"nodemon": "^3.1.9",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.17",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.6" "tsx": "^4.19.2",
"typescript": "^5.7.2",
"vite": "^6.0.7",
"vite-plugin-simple-html": "^0.2.0"
}, },
"dependencies": { "dependencies": {
"@quixo3/prisma-session-store": "^3.1.13" "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@quixo3/prisma-session-store": "^3.1.13",
"@sentry/react": "^8.48.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-toastify": "^11.0.2"
} }
} }
...@@ -10,33 +10,17 @@ ...@@ -10,33 +10,17 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@nextui-org/react": "^2.6.11",
"@fortawesome/react-fontawesome": "^0.2.0",
"@nextui-org/react": "^2.2.9",
"framer-motion": "^11.0.5", "framer-motion": "^11.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"match-sorter": "^6.3.4", "match-sorter": "^8.0.0",
"next-themes": "^0.2.1", "react-apexcharts": "^1.7.0",
"react": "^18.2.0", "react-router-dom": "^7.1.1"
"react-apexcharts": "^1.4.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.1",
"sort-by": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.56", "eslint-plugin-react-refresh": "^0.4.16",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.35", "postcss": "^8.4.35",
"tailwindcss": "^3.4.1", "typescript": "^5.7.2"
"typescript": "^5.2.2",
"vite": "^5.1.4"
} }
} }
...@@ -15,7 +15,7 @@ const options: Props["options"] = { ...@@ -15,7 +15,7 @@ const options: Props["options"] = {
chart: { chart: {
type: "area", type: "area",
animations: { animations: {
easing: "linear", // easing: "linear",
speed: 300, speed: 300,
}, },
sparkline: { sparkline: {
......
import { Spinner } from "@nextui-org/react";
export const LoadingOverlay = () => {
return (
<div className="absolute top-0 left-0 w-full h-full z-[9999] backdrop-blur-sm bg-black/30 text-white flex items-center justify-center">
<Spinner label="Loading..." />
</div>
);
};
import { useTheme } from "next-themes";
import { ToastContainer } from "react-toastify";
export const ToastWrapper = () => {
const { theme } = useTheme()
return (
<ToastContainer
position="bottom-right"
theme={theme}
/>
);
};
\ No newline at end of file
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
faCog, faCog,
faHashtag, faHashtag,
faHome, faHome,
faList,
faServer, faServer,
faShieldHalved, faShieldHalved,
faSquare, faSquare,
...@@ -54,6 +55,12 @@ export const SidebarWrapper = () => { ...@@ -54,6 +55,12 @@ export const SidebarWrapper = () => {
isActive={pathname === "/"} isActive={pathname === "/"}
href="/" href="/"
/> />
<SidebarItem
title="Audit Log"
icon={<FontAwesomeIcon icon={faList} />}
isActive={pathname === "/audit"}
href="/audit"
/>
<CollapseItems <CollapseItems
icon={<FontAwesomeIcon icon={faChartBar} />} icon={<FontAwesomeIcon icon={faChartBar} />}
title="Stats" title="Stats"
...@@ -124,10 +131,10 @@ export const SidebarWrapper = () => { ...@@ -124,10 +131,10 @@ export const SidebarWrapper = () => {
href="/chat/rooms" href="/chat/rooms"
/> />
<SidebarItem <SidebarItem
isActive={pathname === "/chat/settings"} isActive={pathname === "/service/settings"}
title="Settings" title="Settings"
icon={<FontAwesomeIcon icon={faCog} />} icon={<FontAwesomeIcon icon={faCog} />}
href="/chat/settings" href="/service/settings"
/> />
</SidebarMenu> </SidebarMenu>
</div> </div>
......