diff --git a/.env.example b/.env.example index 7e5ce58..ee5dc77 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ -# Example -KUBE_CLUSTER_NAME=example-cluster -GITHUB_TOKEN=example-token \ No newline at end of file +GITHUB_TOKEN= + +# Plausible +BASE_URL=http://plausible.localhost +PLAUSIBLE_DOMAIN=http://plausible.localhost +SECRET_KEY_BASE= +TOTP_VAULT_KEY= \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 665b634..985c2a4 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,8 @@ jobs: - uses: asdf-vm/actions/install@v3 + - run: make lint + - run: make test - run: make build diff --git a/Makefile b/Makefile index 12a3097..6918462 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,10 @@ endif docker login ghcr.io -u default -p $(GITHUB_TOKEN) init: - cp .env.example .env - cp app-config.example.yaml app-config.local.yaml + cp -n .env.example .env || true + cp -n app-config.example.yaml app-config.local.yaml || true -install: +install: init ifneq ($(shell which asdf),) asdf install corepack enable @@ -24,6 +24,10 @@ ifneq ($(shell which asdf),) endif yarn install +lint: install + yarn lint:all + yarn tsc + test: install yarn test @@ -37,7 +41,8 @@ dev-app: dev-backend: yarn workspace backend start -docker-dev: +dev-docker: + @echo "Starting the server at http://localhost:7007" docker compose up logs: @@ -46,14 +51,17 @@ logs: exec: docker compose exec backstage bash -up: - @echo "Starting the server at http://localhost:7007" - docker compose up -d - open http://localhost:7007 +plausible-up: init + @echo "Plausible is running at http://localhost:8000 or http://plausible.localhost" + docker compose -f compose.plausible.yaml up -d -up-logs: - @echo "Starting the server at http://localhost:7007" - docker compose up +plausible-down: + docker compose -f compose.plausible.yaml down + +up: init + @echo "Backstage is running at http://localhost:7007 or http://backstage.localhost" + @echo "Traefik is running at http://localhost:8080 or http://traefik.localhost" + docker compose up -d down: docker compose down @@ -61,7 +69,7 @@ down: run: docker compose run --rm backstage $(filter-out $@,$(MAKECMDGOALS)) -build: +build: init docker compose build backstage publish: login-github version @@ -103,15 +111,15 @@ help: @echo "Available commands:" @echo " init: Initialize the project" @echo " install: Install dependencies" + @echo " lint: Run linting and type checking" @echo " test: Run tests" @echo " dev: Start the development server" @echo " dev-app: Start the app development server" @echo " dev-backend: Start the backend development server" - @echo " docker-dev: Start the development server using Docker" + @echo " dev-docker: Start the development server using Docker" @echo " logs: Show logs" @echo " exec: Execute a command in the backstage container" @echo " up: Start the server" - @echo " up-logs: Start the server and show logs" @echo " down: Stop the server" @echo " run: Run a command in the backstage container" @echo " build: Build the Docker image" diff --git a/app-config.example.yaml b/app-config.example.yaml index 596a532..da03f0e 100644 --- a/app-config.example.yaml +++ b/app-config.example.yaml @@ -20,3 +20,6 @@ catalog: target: ../../examples/all.yaml - type: file target: ../../examples/acme-corp.yaml + +plausible: + enabled: false diff --git a/app-config.production.yaml b/app-config.production.yaml index e397c3b..31c2ad5 100644 --- a/app-config.production.yaml +++ b/app-config.production.yaml @@ -32,3 +32,6 @@ catalog: target: ../../examples/all.yaml - type: file target: ../../examples/acme-corp.yaml + +plausible: + domain: ${PLAUSIBLE_DOMAIN} diff --git a/app-config.yaml b/app-config.yaml index 33e5334..ac2ace1 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -71,3 +71,7 @@ catalog: pullRequestBranchName: backstage-integration rules: - allow: [Component, System, API, Resource, Location, Template, User, Group] + +plausible: + enabled: true + domain: ${PLAUSIBLE_DOMAIN} diff --git a/compose.plausible.yaml b/compose.plausible.yaml new file mode 100644 index 0000000..631a1f4 --- /dev/null +++ b/compose.plausible.yaml @@ -0,0 +1,65 @@ +services: + traefik: + image: traefik:v3.1 + command: + - '--api.insecure=true' + - '--api.dashboard=true' + - '--providers.docker=true' + - '--entrypoints.web.address=:80' + - '--entrypoints.traefik.address=:8080' + ports: + - 80:80 + - 8080:8080 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.traefik.rule=Host(`traefik.localhost`)' + - 'traefik.http.routers.traefik.service=api@internal' + + plausible_db: + # Plausible v2.1.1 was tested against PostgreSQL versions 15 and 16 + # https://github.com/plausible/analytics/blob/v2.1.1/.github/workflows/elixir.yml#L21-L32 + image: postgres:16-alpine + restart: always + volumes: + - db-data:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=postgres + + plausible_events_db: + image: clickhouse/clickhouse-server:24.3.3.102-alpine + restart: always + volumes: + - event-data:/var/lib/clickhouse + - event-logs:/var/log/clickhouse-server + - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro + - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro + ulimits: + nofile: + soft: 262144 + hard: 262144 + + plausible: + image: ghcr.io/plausible/community-edition:v2.1.1 + restart: always + command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run" + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.plausible.rule=Host(`plausible.localhost`)' + - 'traefik.http.services.plausible.loadbalancer.server.port=8000' + depends_on: + - plausible_db + - plausible_events_db + ports: + - 127.0.0.1:8000:8000 + env_file: + - .env + +volumes: + db-data: + driver: local + event-data: + driver: local + event-logs: + driver: local diff --git a/compose.yaml b/compose.yaml index 69bc36b..bae6907 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,13 +1,35 @@ services: + traefik: + image: traefik:v3.1 + command: + - '--api.insecure=true' + - '--api.dashboard=true' + - '--providers.docker=true' + - '--entrypoints.web.address=:80' + - '--entrypoints.traefik.address=:8080' + ports: + - 80:80 + - 8080:8080 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.traefik.rule=Host(`traefik.localhost`)' + - 'traefik.http.routers.traefik.service=api@internal' + backstage: image: ghcr.io/echohello-dev/backstage:${VERSION:-latest} build: context: . dockerfile: Dockerfile + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.backstage.rule=Host(`backstage.localhost`)' + - 'traefik.http.services.backstage.loadbalancer.server.port=7007' ports: - 7007:7007 volumes: - ./app-config.production.yaml:/app/app-config.production.yaml - ./app-config.yaml:/app/app-config.yaml - # env_file: - # - .env + env_file: + - .env diff --git a/docs/development.md b/docs/development.md index 7f5c473..694632a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,5 +1,29 @@ # Development +## Creating a New Plugin + +On the command line, run the following command to create a new plugin. + +```bash +yarn new +``` + +[Documentation](https://backstage.io/docs/conf/defining/) + +### Validating Configuration + +Schemas can be validated using the command: + +```bash +backstage-cli config:check +``` + +To validate and examine the frontend configuration, use the command: + +```bash +backstage-cli config:print --frontend +``` + ## Using Storybook There's a Backstage Storybook site that can be used to view and develop components. diff --git a/docs/tracking.md b/docs/tracking.md new file mode 100644 index 0000000..69f7104 --- /dev/null +++ b/docs/tracking.md @@ -0,0 +1,25 @@ +# Tracking + +We use Plausible Analytics to track visits to our website. Plausible is a privacy-friendly alternative to Google Analytics. It does not use cookies and does not collect any personal data. You can read more about Plausible's data policy [here](https://plausible.io/data-policy). + +## Local Development + +To run the Plausible Analytics server locally, you can use the following command: + +```bash +make plausible-up +``` + +Build the app and start the server: + +```bash +make build +make up +``` + +To tear down the server: + +```bash +make plausible-down +make down +``` diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js index e2a53a6..5073122 100644 --- a/packages/app/.eslintrc.js +++ b/packages/app/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }], + }, +}); diff --git a/packages/app/package.json b/packages/app/package.json index b2aa984..14d2de6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -41,6 +41,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@fontsource/inter": "^5.0.18", + "@internal/backstage-plugin-plausible": "^0.1.0", "@mui/icons-material": "^5.15.19", "@mui/joy": "^5.0.0-beta.36", "@mui/material": "^5.15.19", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index f67cdcb..bff2d1a 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -41,6 +41,7 @@ import { UnifiedThemeProvider } from '@backstage/theme'; import { backstageTheme } from './theme'; import { HomePage } from './components/home/HomePage'; import CssBaseline from '@mui/material/CssBaseline'; +import { PlausibleAnalytics } from '@internal/backstage-plugin-plausible'; const app = createApp({ apis, @@ -119,6 +120,7 @@ const routes = ( export default app.createRoot( <> + diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 16cce8f..42d0b73 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -1,7 +1,6 @@ -import React, { PropsWithChildren, useState } from 'react'; +import React, { PropsWithChildren } from 'react'; import { styled } from '@mui/material/styles'; import Home from '@mui/icons-material/Home'; -import CreateComponent from '@mui/icons-material/AddCircleOutline'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { @@ -33,6 +32,7 @@ import ScoreRoundedIcon from '@mui/icons-material/ScoreRounded'; import { IconComponent } from '@backstage/core-plugin-api'; export enum LocalStorageKeys { + // eslint-disable-next-line no-unused-vars SIDEBAR_PIN_STATE = 'sidebarPinState', } diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx index 3345a94..f71b676 100644 --- a/packages/app/src/components/home/HomePage.tsx +++ b/packages/app/src/components/home/HomePage.tsx @@ -15,7 +15,7 @@ import Groups3Icon from '@mui/icons-material/Groups3'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import TrendingUpIcon from '@mui/icons-material/TrendingUp'; import TrendingDownIcon from '@mui/icons-material/TrendingDown'; -import React, { useRef } from 'react'; +import React from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import CardContent from '@mui/material/CardContent'; @@ -76,26 +76,6 @@ export const HomePage = () => { const [isSquad, setIsSquad] = React.useState(true); const theme = useTheme(); - const scrollContainerRef = useRef(null); - - const scrollLeft = () => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollBy({ - left: -300, // Adjust the scroll amount as needed - behavior: 'smooth', - }); - } - }; - - const scrollRight = () => { - if (scrollContainerRef.current) { - scrollContainerRef.current.scrollBy({ - left: 300, // Adjust the scroll amount as needed - behavior: 'smooth', - }); - } - }; - const months = [ new Date(2021, 7, 1), new Date(2021, 8, 1), diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js index e2a53a6..5073122 100644 --- a/packages/backend/.eslintrc.js +++ b/packages/backend/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }], + }, +}); diff --git a/plugins/plausible/.eslintrc.js b/plugins/plausible/.eslintrc.js new file mode 100644 index 0000000..e2a53a6 --- /dev/null +++ b/plugins/plausible/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/plausible/README.md b/plugins/plausible/README.md new file mode 100644 index 0000000..8b35e20 --- /dev/null +++ b/plugins/plausible/README.md @@ -0,0 +1,13 @@ +# plausible + +Welcome to the plausible plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/plausible](http://localhost:3000/plausible). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/plugins/plausible/config.d.ts b/plugins/plausible/config.d.ts new file mode 100644 index 0000000..e809364 --- /dev/null +++ b/plugins/plausible/config.d.ts @@ -0,0 +1,13 @@ +export interface Config { + plausible?: { + /** + * @visibility frontend + */ + enabled: boolean; + + /** + * @visibility frontend + */ + domain: string; + }; +} diff --git a/plugins/plausible/dev/index.tsx b/plugins/plausible/dev/index.tsx new file mode 100644 index 0000000..f0c5e37 --- /dev/null +++ b/plugins/plausible/dev/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { createDevApp } from '@backstage/dev-utils'; +import { plausiblePlugin } from '../src/plugin'; + +createDevApp().registerPlugin(plausiblePlugin).render(); diff --git a/plugins/plausible/package.json b/plugins/plausible/package.json new file mode 100644 index 0000000..95818ed --- /dev/null +++ b/plugins/plausible/package.json @@ -0,0 +1,54 @@ +{ + "name": "@internal/backstage-plugin-plausible", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/core-components": "^0.14.8", + "@backstage/core-plugin-api": "^1.9.3", + "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.9.13", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.61", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.26.10", + "@backstage/core-app-api": "^1.13.0", + "@backstage/dev-utils": "^1.0.34", + "@backstage/test-utils": "^1.5.7", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", + "msw": "^1.0.0", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/plausible/src/components/PlausibleAnalytics.tsx b/plugins/plausible/src/components/PlausibleAnalytics.tsx new file mode 100644 index 0000000..c2ec17c --- /dev/null +++ b/plugins/plausible/src/components/PlausibleAnalytics.tsx @@ -0,0 +1,20 @@ +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import React from 'react'; + +export const PlausibleAnalytics = () => { + const config = useApi(configApiRef); + const enabled = config.getOptionalBoolean('plausible.enabled') ?? false; + const domain = config.getOptionalString('plausible.domain'); + + if (!enabled || !domain) { + return null; + } + + return ( +