This commit is contained in:
Darren Clarke 2024-03-17 12:58:25 +01:00
parent f62c9f064d
commit b8c6e893ff
43 changed files with 4721 additions and 1807 deletions

View file

@ -1,6 +1,11 @@
"use client";
import { FC } from "react";
import { ZammadWrapper } from "./ZammadWrapper";
import { OpenSearchWrapper } from "leafcutter-common";
export const Home: FC = () => <ZammadWrapper path="#dashboard" hideSidebar />;
export const Home: FC = () => (
<OpenSearchWrapper
url="/app/dashboards#/view/c39012d0-eb7a-11ed-8e00-17d7d50cd7b2?embed=true&tenant=global"
marginTop="0"
/>
);

View file

@ -6,10 +6,5 @@ export const metadata: Metadata = {
};
export default function Page() {
return (
<iframe src="/opensearch/app/dashboards?security_tenant=global#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?embed=true&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!f%2Cvalue%3A900000)%2Ctime%3A(from%3Anow-7d%2Cto%3Anow))&hide-filter-bar=true"
height="1000"
width="1200"
></iframe>
);
return <Home />;
}

View file

@ -29,32 +29,16 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
const labelStudioURL = process.env.LABEL_STUDIO_URL ?? "http://label-studio:8080";
const { token } = request.nextauth;
const headers = { 'X-Forwarded-User': token?.email?.toLowerCase() };
console.log ({ pathname: request.nextUrl.pathname});
console.log({ pathname: request.nextUrl.pathname });
if (request.nextUrl.pathname.startsWith('/api/v1/configuration/account') ||
request.nextUrl.pathname.startsWith('/api/v1/restapiinfo') ||
request.nextUrl.pathname.startsWith('/api/v1/auth') ||
request.nextUrl.pathname.startsWith('/api/core') ||
request.nextUrl.pathname.startsWith('/api/dataconnections') ||
request.nextUrl.pathname.startsWith('/api/v1/multitenancy') ||
request.nextUrl.pathname.startsWith('/api/ism') ||
request.nextUrl.pathname.startsWith('/node_modules') ||
request.nextUrl.pathname.startsWith('/translations') || request.nextUrl.pathname.startsWith('/6867') || request.nextUrl.pathname.startsWith('/ui') || request.nextUrl.pathname.startsWith('/bootstrap')) {
const headers = {
'x-proxy-user': "admin",
'x-proxy-roles': "all_access",
// 'X-Forwarded-For': "link"
};
return rewriteURL(request, `${linkBaseURL}`, opensearchURL, headers);
}
else if (request.nextUrl.pathname.startsWith('/opensearch')) {
if (request.nextUrl.pathname.startsWith('/opensearch')) {
const headers = {
'x-proxy-user': "admin",
'x-proxy-roles': "all_access",
// 'X-Forwarded-For': "link"
};
return rewriteURL(request, `${linkBaseURL}/opensearch`, opensearchURL, headers);
}else if (request.nextUrl.pathname.startsWith('/metamigo')) {
} else if (request.nextUrl.pathname.startsWith('/metamigo')) {
return rewriteURL(request, `${linkBaseURL}/metamigo`, metamigoURL);
} else if (request.nextUrl.pathname.startsWith('/label-studio')) {
return rewriteURL(request, `${linkBaseURL}/label-studio`, labelStudioURL);

View file

@ -39,7 +39,8 @@
"sharp": "^0.33.2",
"swr": "^2.2.5",
"tss-react": "^4.9.4",
"twilio-client": "^1.15.1"
"twilio-client": "^1.15.1",
"ui": "*"
},
"devDependencies": {
"@babel/core": "^7.24.0",

View file

@ -0,0 +1,14 @@
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "./database";
export const authOptions = NextAuth({
adapter: KyselyAdapter(db),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
})

View file

@ -0,0 +1,62 @@
import { PostgresDialect } from "kysely";
import { Pool } from "pg";
import { KyselyAuth } from "@auth/kysely-adapter";
import { CamelCasePlugin } from "kysely";
import type { GeneratedAlways } from "kysely";
interface Database {
User: {
id: GeneratedAlways<string>;
name: string | null;
email: string;
emailVerified: Date | null;
image: string | null;
};
Account: {
id: GeneratedAlways<string>;
userId: string;
type: string;
provider: string;
providerAccountId: string;
refresh_token: string | null;
access_token: string | null;
expires_at: number | null;
token_type: string | null;
scope: string | null;
id_token: string | null;
session_state: string | null;
};
Session: {
id: GeneratedAlways<string>;
userId: string;
sessionToken: string;
expires: Date;
};
VerificationToken: {
identifier: string;
token: string;
expires: Date;
};
WhatsAppBot: {
id: GeneratedAlways<string>;
userId: string;
phone: string;
password: string;
};
}
export const db = new KyselyAuth<Database>({
dialect: new PostgresDialect({
pool: new Pool({
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
}),
}),
plugins: [new CamelCasePlugin()],
});

View file

@ -0,0 +1,6 @@
import NextAuth from "next-auth";
import { authOptions } from "@/app/_lib/authentication";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View file

@ -1,7 +1,7 @@
"use client";
import { GridColDef } from "@mui/x-data-grid-pro";
import { List } from "@/app/_components/List";
import { List } from "ui";
export default function Page() {
const columns: GridColDef[] = [

View file

@ -1,7 +1,7 @@
"use client";
import { GridColDef } from "@mui/x-data-grid-pro";
import { List } from "@/app/_components/List";
import { List } from "ui";
export default function Page() {
const columns: GridColDef[] = [

View file

@ -1,7 +1,7 @@
"use client";
import { GridColDef } from "@mui/x-data-grid-pro";
import { List } from "@/app/_components/List";
import { List } from "ui";
export default function Page() {
const columns: GridColDef[] = [

View file

@ -1,7 +1,7 @@
"use client";
import { GridColDef } from "@mui/x-data-grid-pro";
import { List } from "@/app/_components/List";
import { List } from "ui";
export default function Page() {
const columns: GridColDef[] = [

View file

@ -1,7 +1,7 @@
"use client";
import { GridColDef } from "@mui/x-data-grid-pro";
import { List } from "@/app/_components/List";
import { List } from "ui";
export default function Page() {
const columns: GridColDef[] = [

View file

@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
transpilePackages: ["ui"]
};
export default nextConfig;

View file

@ -9,37 +9,40 @@
"lint": "next lint"
},
"dependencies": {
"@auth/kysely-adapter": "^0.6.0",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.168",
"@mui/material": "^5",
"@mui/material-nextjs": "^5.15.11",
"@mui/x-data-grid-pro": "^6.19.6",
"@mui/x-date-pickers-pro": "^6.19.7",
"date-fns": "^3.5.0",
"kysely": "^0.27.3",
"leafcutter-common": "*",
"kysely": "^0.26.1",
"material-ui-popup-state": "^5.0.10",
"mui-chips-input": "^2.1.4",
"next": "14.1.3",
"next-auth": "^4.24.7",
"pg": "^8.11.3",
"react": "18.2.0",
"react-cookie": "^7.1.0",
"react-digit-input": "^2.1.0",
"react-dom": "18.2.0",
"react-polyglot": "^0.7.2",
"react-qr-code": "^2.0.12",
"react-timer-hook": "^3.0.7",
"tss-react": "^4.9.4"
"tss-react": "^4.9.4",
"ui": "*"
},
"devDependencies": {
"@types/node": "^20",
"@types/pg": "^8.11.2",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.1.3",
"typescript": "^5"
"typescript": "^5",
"ts-config": "*"
}
}

View file

@ -6,7 +6,7 @@
"author": "Abel Luck <abel@guardianproject.info>",
"license": "AGPL-3.0-or-later",
"dependencies": {
"graphile-worker": "^0.13.0",
"graphile-worker": "^0.16.4",
"html-to-text": "^9.0.5",
"node-fetch": "^3",
"pg-promise": "^11.5.4",
@ -14,6 +14,7 @@
"twilio": "^5.0.1"
},
"devDependencies": {
"ts-config": "*",
"@babel/core": "7.24.0",
"@babel/preset-env": "7.24.0",
"@babel/preset-typescript": "7.23.3",

View file

@ -1,8 +1,24 @@
version: "3.4"
x-metamigo-vars:
&common-metamigo-variables
DATABASE_HOST: "metamigo-postgresql"
x-global-vars: &common-global-variables
TZ: Etc/UTC
x-zammad-vars: &common-zammad-variables
MEMCACHE_SERVERS: "zammad-memcached:11211"
REDIS_URL: "redis://zammad-redis:6379"
POSTGRESQL_HOST: "postgresql"
POSTGRESQL_PORT: "5432"
POSTGRESQL_USER: "zammad"
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
POSTGRESQL_DB: "zammad_production"
ELASTICSEARCH_HOST: ${OPENSEARCH_HOST}
ELASTICSEARCH_USER: ${OPENSEARCH_USER}
ELASTICSEARCH_PASS: ${OPENSEARCH_PASS}
ELASTICSEARCH_SSL_VERIFY: false # this doesn't set es_ssl_verify as expected, but ideally it would
ELASTICSEARCH_SCHEMA: "https"
x-metamigo-vars: &common-metamigo-variables
DATABASE_HOST: "postgresql"
DATABASE_NAME: "metamigo"
DATABASE_ROOT_OWNER: "root"
DATABASE_ROOT_PASSWORD: ${METAMIGO_DATABASE_ROOT_PASSWORD}
@ -32,22 +48,34 @@ x-metamigo-vars:
SIGNALD_SOCKET: /signald/signald.sock
services:
metamigo-postgresql:
postgresql:
container_name: postgresql
environment:
<<:
[
*common-global-variables,
*common-zammad-variables,
*common-metamigo-variables,
]
POSTGRES_USER: zammad
POSTGRES_PASSWORD: ${ZAMMAD_DATABASE_PASSWORD}
build: ../postgresql
image: registry.gitlab.com/digiresilience/link/link-stack/postgresql:${LINK_STACK_VERSION}
container_name: metamigo-postgresql
restart: ${RESTART}
volumes:
- metamigo-data:/var/lib/postgresql/data
- ./scripts/bootstrap-metamigo.sh:/docker-entrypoint-initdb.d/bootstrap-metamigo.sh
environment:
<<: *common-metamigo-variables
POSTGRES_PASSWORD: ${METAMIGO_DATABASE_ROOT_PASSWORD}
POSTGRES_USER: "root"
POSTGRES_DB: "metamigo"
ports:
- 127.0.0.1:5433:5432
- 5432:5432
volumes:
- postgresql-data:/var/lib/postgresql/data
# volumes:
# - metamigo-data:/var/lib/postgresql/data
# - ./scripts/bootstrap-metamigo.sh:/docker-entrypoint-initdb.d/bootstrap-metamigo.sh
#environment:
# <<: *common-metamigo-variables
# POSTGRES_PASSWORD: ${METAMIGO_DATABASE_ROOT_PASSWORD}
# POSTGRES_USER: "root"
# POSTGRES_DB: "metamigo"
volumes:
metamigo-data:
postgresql-data:
driver: local

View file

@ -1,21 +1,19 @@
version: "3.4"
x-global-vars:
&common-global-variables
x-global-vars: &common-global-variables
TZ: Etc/UTC
x-zammad-vars:
&common-zammad-variables
x-zammad-vars: &common-zammad-variables
MEMCACHE_SERVERS: "zammad-memcached:11211"
REDIS_URL: "redis://zammad-redis:6379"
POSTGRESQL_HOST: "zammad-postgresql"
POSTGRESQL_HOST: "postgresql"
POSTGRESQL_PORT: "5432"
POSTGRESQL_USER: "zammad"
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
POSTGRESQL_DB: "zammad_production"
ELASTICSEARCH_HOST: ${OPENSEARCH_HOST}
ELASTICSEARCH_USER: ${OPENSEARCH_USER}
ELASTICSEARCH_PASS: ${OPENSEARCH_PASS}
ELASTICSEARCH_PASS: ${OPENSEARCH_ADMIN_PASSWORD}
ELASTICSEARCH_SSL_VERIFY: false # this doesn't set es_ssl_verify as expected, but ideally it would
ELASTICSEARCH_SCHEMA: "https"
@ -23,11 +21,11 @@ services:
zammad-init:
platform: linux/x86_64
container_name: zammad-init
command: [ "zammad-init" ]
command: ["zammad-init"]
depends_on:
- zammad-postgresql
- postgresql
environment:
<<: [ *common-zammad-variables, *common-global-variables ]
<<: [*common-zammad-variables, *common-global-variables]
POSTGRESQL_USER: zammad
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
build:
@ -53,7 +51,7 @@ services:
zammad-nginx:
platform: linux/x86_64
container_name: zammad-nginx
command: [ "zammad-nginx" ]
command: ["zammad-nginx"]
expose:
- "8080"
ports:
@ -75,30 +73,16 @@ services:
- zammad-config-nginx:/etc/nginx/sites-enabled:ro
- zammad-var:/opt/zammad/var:ro
zammad-postgresql:
container_name: zammad-postgresql
environment:
<<: [ *common-global-variables, *common-zammad-variables ]
POSTGRES_USER: zammad
POSTGRES_PASSWORD: ${ZAMMAD_DATABASE_PASSWORD}
build: ../postgresql
image: registry.gitlab.com/digiresilience/link/link-stack/postgresql:${LINK_STACK_VERSION}
restart: ${RESTART}
ports:
- 5432:5432
volumes:
- postgresql-data:/var/lib/postgresql/data
zammad-railsserver:
platform: linux/x86_64
container_name: zammad-railsserver
command: [ "zammad-railsserver" ]
command: ["zammad-railsserver"]
depends_on:
- zammad-memcached
- zammad-postgresql
- zammad-redis
- postgresql
environment:
<<: [ *common-global-variables, *common-zammad-variables ]
<<: [*common-global-variables, *common-zammad-variables]
RAILS_RELATIVE_URL_ROOT: /zammad
build:
context: ../zammad
@ -123,13 +107,13 @@ services:
zammad-scheduler:
platform: linux/x86_64
container_name: zammad-scheduler
command: [ "zammad-scheduler" ]
command: ["zammad-scheduler"]
depends_on:
- zammad-memcached
- zammad-railsserver
- zammad-redis
environment:
<<: [ *common-global-variables, *common-zammad-variables ]
<<: [*common-global-variables, *common-zammad-variables]
build:
context: ../zammad
args:
@ -143,13 +127,13 @@ services:
zammad-websocket:
platform: linux/x86_64
container_name: zammad-websocket
command: [ "zammad-websocket" ]
command: ["zammad-websocket"]
depends_on:
- zammad-memcached
- zammad-railsserver
- zammad-redis
environment:
<<: [ *common-global-variables, *common-zammad-variables ]
<<: [*common-global-variables, *common-zammad-variables]
build:
context: ../zammad
args:
@ -161,10 +145,6 @@ services:
- zammad-storage:/opt/zammad/storage
volumes:
opensearch-data:
driver: local
postgresql-data:
driver: local
redis-data:
driver: local
zammad-config-nginx:

View file

@ -2,14 +2,22 @@ opensearch.hosts: [https://opensearch:9200]
opensearch.ssl.verificationMode: none
opensearch.username: kibanaserver
opensearch.password: kibanaserver
opensearch.requestHeadersAllowlist: ["securitytenant","Authorization","x-forwarded-for","x-proxy-user","x-proxy-roles"]
opensearch_security.auth.type: "proxy"
opensearch_security.proxycache.user_header: "x-proxy-user"
opensearch_security.proxycache.roles_header: "x-proxy-roles"
opensearch.requestHeadersAllowlist:
[
"securitytenant",
"Authorization",
"x-forwarded-for",
"x-proxy-user",
"x-proxy-roles",
]
# opensearch_security.auth.type: "proxy"
# opensearch_security.proxycache.user_header: "x-proxy-user"
# opensearch_security.proxycache.roles_header: "x-proxy-roles"
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.preferred: [Private, Global]
opensearch_security.readonly_mode.roles: [kibana_read_only]
# Use this setting if you are running opensearch-dashboards without https
opensearch_security.cookie.secure: false
server.host: '0.0.0.0'
server.host: "0.0.0.0"
server.basePath: "/opensearch"
server.rewriteBasePath: false

View file

@ -15,17 +15,17 @@ config:
description: "Authenticate via HTTP Basic against internal users database"
http_enabled: true
transport_enabled: true
order: 4
order: 0
http_authenticator:
type: basic
challenge: true
challenge: false
authentication_backend:
type: intern
proxy_auth_domain:
description: "Authenticate via proxy"
http_enabled: true
transport_enabled: true
order: 0
order: 1
http_authenticator:
type: proxy
challenge: false

View file

@ -22,5 +22,9 @@ const finalCommand = command === "up" ? ["up", "-d"] : [command];
const dockerCompose = spawn('docker', ['compose', '--env-file', '.env', ...finalFiles, ...finalCommand]);
dockerCompose.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
console.log(`${data}`);
});
dockerCompose.stderr.on('data', (data) => {
console.log(`${data}`);
});

5328
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,41 +1,40 @@
{
"name": "link-stack",
"version": "2.1.0",
"description": "",
"main": "index.js",
"version": "2.2.0",
"description": "Link from the Center for Digital Resilience",
"scripts": {
"dev": "dotenv -- turbo run dev --concurrency 30",
"build": "turbo build --concurrency 30",
"dev": "dotenv -- turbo run dev",
"build": "turbo build",
"migrate": "dotenv -- npm run migrate --workspace=database",
"fmt": "turbo run fmt",
"docker:all:up": "node scripts/docker.js all up",
"docker:all:down": "node scripts/docker.js all down",
"docker:all:build": "node scripts/docker.js all build",
"docker:link:dev:up": "node scripts/docker.js linkDev up",
"docker:link:dev:down": "node scripts/docker.js linkDev down",
"docker:link:up": "node scripts/docker.js link up",
"docker:link:down": "node scripts/docker.js link down",
"docker:link:build": "node scripts/docker.js link build",
"docker:opensearch:up": "node scripts/docker.js opensearch up",
"docker:opensearch:down": "node scripts/docker.js opensearch down",
"docker:opensearch:build": "node scripts/docker.js opensearch build",
"docker:leafcutter:dev:up": "node scripts/docker.js leafcutterDev up",
"docker:leafcutter:dev:down": "node scripts/docker.js leafcutterDev down",
"docker:leafcutter:up": "node scripts/docker.js leafcutter up",
"docker:leafcutter:down": "node scripts/docker.js leafcutter down",
"docker:leafcutter:build": "node scripts/docker.js leafcutter build",
"docker:zammad:up": "node scripts/docker.js zammad up",
"docker:zammad:down": "node scripts/docker.js zammad down",
"docker:zammad:build": "node scripts/docker.js zammad build",
"docker:metamigo:dev:up": "node scripts/docker.js metamigoDev up",
"docker:metamigo:dev:down": "node scripts/docker.js metamigoDev down",
"docker:metamigo:up": "node scripts/docker.js metamigo up",
"docker:metamigo:down": "node scripts/docker.js metamigo down",
"docker:metamigo:build": "node scripts/docker.js metamigo build",
"upgrade:setup": "npm i -g npm-check-updates",
"upgrade:check": "ncu && ncu -ws",
"upgrade:all": "ncu -u && ncu -ws -u && npm i",
"clean": "rm -f package-lock.json && rm -rf node_modules && rm -rf apps/*/node_modules && rm -rf packages/*/node_modules && rm -rf apps/*/.next && rm -rf packages/*/.turbo && rm -rf apps/*/.turbo && rm -rf docker/zammad/addons/*"
"clean": "rm -f package-lock.json && rm -rf node_modules && rm -rf apps/*/node_modules && rm -rf packages/*/node_modules && rm -rf apps/*/.next && rm -rf packages/*/.turbo && rm -rf apps/*/.turbo && rm -rf docker/zammad/addons/*",
"docker:all:up": "node docker/scripts/docker.js all up",
"docker:all:down": "node docker/scripts/docker.js all down",
"docker:all:build": "node docker/scripts/docker.js all build",
"docker:link:dev:up": "node docker/scripts/docker.js linkDev up",
"docker:link:dev:down": "node docker/scripts/docker.js linkDev down",
"docker:link:up": "node docker/scripts/docker.js link up",
"docker:link:down": "node docker/scripts/docker.js link down",
"docker:link:build": "node docker/scripts/docker.js link build",
"docker:opensearch:up": "node docker/scripts/docker.js opensearch up",
"docker:opensearch:down": "node docker/scripts/docker.js opensearch down",
"docker:opensearch:build": "node docker/scripts/docker.js opensearch build",
"docker:leafcutter:dev:up": "node docker/scripts/docker.js leafcutterDev up",
"docker:leafcutter:dev:down": "node docker/scripts/docker.js leafcutterDev down",
"docker:leafcutter:up": "node docker/scripts/docker.js leafcutter up",
"docker:leafcutter:down": "node docker/scripts/docker.js leafcutter down",
"docker:leafcutter:build": "node docker/scripts/docker.js leafcutter build",
"docker:zammad:up": "node docker/scripts/docker.js zammad up",
"docker:zammad:down": "node docker/scripts/docker.js zammad down",
"docker:zammad:build": "node docker/scripts/docker.js zammad build",
"docker:metamigo:dev:up": "node docker/scripts/docker.js metamigoDev up",
"docker:metamigo:dev:down": "node docker/scripts/docker.js metamigoDev down",
"docker:metamigo:up": "node docker/scripts/docker.js metamigo up",
"docker:metamigo:down": "node docker/scripts/docker.js metamigo down",
"docker:metamigo:build": "node docker/scripts/docker.js metamigo build"
},
"workspaces": [
"apps/*",
@ -50,11 +49,10 @@
"author": "Darren Clarke",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"dotenv-cli": "latest",
"prettier": "^3.2.5"
"dotenv-cli": "latest"
},
"engines": {
"npm": ">=9.6.7",
"npm": ">=10",
"node": ">=20"
}
}

View file

@ -34,7 +34,7 @@ export const OpenSearchWrapper: FC<OpenSearchWrapperProps> = ({
>
<Iframe
id="opensearch"
url={`${process.env.NEXT_PUBLIC_NEXTAUTH_URL}${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`}
url={`/opensearch/${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`}
width="100%"
height="100%"
frameBorder={0}

View file

@ -9,6 +9,7 @@ export { Tooltip } from "./components/Tooltip";
export { Preview } from "./components/Preview";
export { GettingStartedDialog } from "./components/GettingStartedDialog";
export { VisualizationDetail } from "./components/VisualizationDetail";
export { OpenSearchWrapper } from "./components/OpenSearchWrapper";
export const locales = {
en,
fr,

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,8 @@
{
"name": "tsconfig",
"name": "ts-config",
"version": "0.1.4",
"description": "Shared TypeScript config",
"license": "AGPL-3.0-or-later",
"repository": "https://gitlab.com/digiresilience.org/link/tsconfig-amigo",
"author": "Abel Luck <abel@guardianproject.info>",
"main": "tsconfig.json",
"engines": {

View file

@ -0,0 +1,26 @@
"use client";
import { FC } from "react";
import { Grid, Box } from "@mui/material";
import { typography } from "../styles/theme";
interface DetailProps {
title: string;
entity: string;
children: any;
}
export const Detail: FC<DetailProps> = ({ title, entity, children }) => {
const { h3 } = typography;
return (
<Box sx={{ height: "100vh", backgroundColor: "#ddd", p: 3 }}>
<Grid container direction="column">
<Grid item>
<Box sx={h3}>{title}</Box>
</Grid>
<Grid item>{children}</Grid>
</Grid>
</Box>
);
};

View file

@ -0,0 +1,67 @@
"use client";
import { FC } from "react";
import {
Grid,
Button,
Dialog as MUIDialog,
DialogActions,
DialogContent,
TextField,
Autocomplete,
} from "@mui/material";
import { typography } from "../styles/theme";
interface DialogProps {
title: string;
open: boolean;
closeDialog: () => void;
children?: any;
}
export const Dialog: FC<DialogProps> = ({
title,
open,
closeDialog,
children,
}) => {
return (
<MUIDialog open={open} maxWidth="md" fullWidth>
<DialogContent>{children}</DialogContent>
<DialogActions sx={{ px: 3, pt: 0, pb: 3 }}>
<Grid container justifyContent="space-between">
<Grid item>
<Button
sx={{
backgroundColor: "white",
color: "#666",
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
}}
onClick={() => {
closeDialog();
}}
>
Cancel
</Button>
</Grid>
<Grid item>
<Button
sx={{
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
px: 3,
}}
>
Create Ticket
</Button>
</Grid>
</Grid>
</DialogActions>
</MUIDialog>
);
};

View file

@ -0,0 +1,86 @@
"use client";
import { FC } from "react";
import { Grid, Box } from "@mui/material";
import { DataGridPro, GridColDef } from "@mui/x-data-grid-pro";
import { typography } from "../styles/theme";
interface ListProps {
title: string;
rows: any;
columns: GridColDef<any>[];
onRowClick: (id: string) => void;
buttons?: React.ReactNode;
}
export const List: FC<ListProps> = ({ title, rows, columns, onRowClick, buttons }) => {
const { h3 } = typography;
return (
<Box sx={{ height: "100vh", backgroundColor: "#ddd", p: 3 }}>
<Grid container direction="column">
<Grid item container direction="row" justifyContent="space-between">
<Grid item>
<Box sx={h3}>{title}</Box>
</Grid>
<Grid item>
{buttons}
</Grid>
</Grid>
<Grid item>
<Box
sx={{
backgroundColor: "#ddd",
border: 0,
width: "100%",
height: "100vh",
".MuiDataGrid-row": {
cursor: "pointer",
"&:hover": {
backgroundColor: "#1982fc33 !important",
fontWeight: "bold",
},
},
".MuiDataGrid-row:nth-of-type(1n)": {
backgroundColor: "#f3f3f3",
},
".MuiDataGrid-row:nth-of-type(2n)": {
backgroundColor: "#fff",
},
".MuiDataGrid-columnHeaderTitle": {
color: "#333",
fontWeight: "bold",
fontSize: 11,
margin: "0 auto",
},
".MuiDataGrid-columnHeader": {
backgroundColor: "#ccc",
border: 0,
borderBottom: "3px solid #ddd",
},
}}
>
<DataGridPro
rows={rows}
columns={columns}
density="compact"
pagination
initialState={{
pagination: { paginationModel: { pageSize: 25 } },
}}
pageSizeOptions={[5, 10, 25]}
paginationMode="client"
sx={{ height: "100vh" }}
rowBuffer={30}
rowHeight={46}
scrollbarSize={0}
disableVirtualization
disableColumnMenu
onRowClick={(row: any) => onRowClick(row.id)}
/>
</Box>
</Grid>
</Grid>
</Box>
);
};

View file

@ -0,0 +1,350 @@
"use client";
import { FC } from "react";
import {
Box,
Grid,
Typography,
List,
ListItemButton,
ListItemIcon,
ListItemText,
ListItemSecondaryAction,
Drawer,
} from "@mui/material";
import {
ExpandCircleDown as ExpandCircleDownIcon,
AccountCircle as AccountCircleIcon,
Chat as ChatIcon,
PermPhoneMsg as PhoneIcon,
WhatsApp as WhatsAppIcon,
Facebook as FacebookIcon,
} from "@mui/icons-material";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { fonts } from "../styles/theme";
// import LinkLogo from "@/public/link-logo-small.png";
// import { useSession, signOut } from "next-auth/react";
const openWidth = 270;
const closedWidth = 100;
const MenuItem = ({
name,
href,
Icon,
iconSize,
inset = false,
selected = false,
open = true,
badge,
target = "_self",
}: any) => (
<Link href={href} target={target}>
<ListItemButton
sx={{
p: 0,
mb: 1,
bl: iconSize === 0 ? "1px solid white" : "inherit",
}}
selected={selected}
>
{iconSize > 0 ? (
<ListItemIcon
sx={{
color: `white`,
minWidth: 0,
mr: 2,
textAlign: "center",
margin: open ? "0 8 0 0" : "0 auto",
}}
>
<Box
sx={{
width: iconSize,
height: iconSize,
mr: 0.5,
mt: "-4px",
}}
>
<Icon />
</Box>
</ListItemIcon>
) : (
<Box
sx={{
width: 30,
height: "28px",
position: "relative",
ml: "9px",
mr: "1px",
}}
>
<Box
sx={{
width: "1px",
height: "56px",
backgroundColor: "white",
position: "absolute",
left: "3px",
top: "-10px",
}}
/>
<Box
sx={{
width: "42px",
height: "42px",
position: "absolute",
top: "-27px",
left: "3px",
border: "solid 1px #fff",
borderColor: "transparent transparent transparent #fff",
borderRadius: "60px",
rotate: "-35deg",
}}
/>
</Box>
)}
{open && (
<ListItemText
inset={inset}
primary={
<Typography
variant="body1"
sx={{
fontSize: 16,
fontWeight: "bold",
border: 0,
textAlign: "left",
color: "white",
}}
>
{name}
</Typography>
}
/>
)}
{badge && badge > 0 ? (
<ListItemSecondaryAction>
<Typography
color="textSecondary"
variant="body1"
className="badge"
sx={{
backgroundColor: "#FFB620",
color: "black !important",
borderRadius: 10,
px: 1,
fontSize: 12,
fontWeight: "bold",
}}
>
{badge}
</Typography>
</ListItemSecondaryAction>
) : null}
</ListItemButton>
</Link>
);
interface SidebarProps {
open: boolean;
setOpen: (open: boolean) => void;
}
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
const pathname = usePathname();
const { poppins } = fonts;
// const { data: session } = useSession();
// const username = session?.user?.name || "User";
// const logout = () => {
// signOut({ callbackUrl: "/login" });
// };
return (
<Drawer
sx={{ width: open ? openWidth : closedWidth, flexShrink: 0 }}
variant="permanent"
anchor="left"
open={open}
PaperProps={{
sx: {
width: open ? openWidth : closedWidth,
border: 0,
overflow: "visible",
},
}}
>
<Box
sx={{
position: "absolute",
top: 20,
right: open ? -8 : -16,
color: "#1C75FD",
rotate: open ? "90deg" : "-90deg",
}}
onClick={() => {
setOpen!(!open);
}}
>
<ExpandCircleDownIcon
sx={{
width: 30,
height: 30,
background: "white",
borderRadius: 500,
}}
/>
</Box>
<Grid
container
direction="column"
justifyContent="space-between"
wrap="nowrap"
spacing={0}
sx={{ backgroundColor: "#25272A", height: "100%", p: 2 }}
>
<Grid item container>
<Grid item sx={{ width: open ? "40px" : "100%" }}>
<Box
sx={{
width: "40px",
height: "40px",
margin: open ? "0" : "0 auto",
}}
>
<Image
src={"" /* LinkLogo */}
alt="Link logo"
width={40}
height={40}
style={{
objectFit: "cover",
filter: "grayscale(100) brightness(100)",
}}
/>
</Box>
.
</Grid>
{open && (
<Grid item>
<Typography
variant="h2"
sx={{
fontSize: 26,
color: "white",
fontWeight: 700,
mt: 1,
ml: 0.5,
fontFamily: poppins.style.fontFamily,
}}
>
Metamigo
</Typography>
</Grid>
)}
</Grid>
<Grid item>
<Box
sx={{
height: "0.5px",
width: "100%",
backgroundColor: "#666",
mb: 1,
}}
/>
</Grid>
<Grid
item
container
direction="column"
sx={{
mt: "6px",
overflow: "scroll",
scrollbarWidth: "none",
msOverflowStyle: "none",
"&::-webkit-scrollbar": { display: "none" },
}}
flexGrow={1}
>
<List
component="nav"
sx={{
a: {
textDecoration: "none",
".MuiListItemButton-root": {
p: 1,
borderRadius: 2,
"&:hover": {
background: "#555",
},
".MuiTypography-root": {
p: {
color: "#999 !important",
fontSize: 16,
},
},
".badge": {
p: { fontSize: 12, color: "black !important" },
},
},
".Mui-selected": {
background: "#444",
color: "#fff !important",
".MuiTypography-root": {
p: {
color: "#fff !important",
fontSize: 16,
},
},
".badge": {
p: { fontSize: 12, color: "black !important" },
},
},
},
}}
>
<MenuItem
name="Whatsapp"
href="/whatsapp"
selected={pathname.endsWith("/whatsapp")}
Icon={WhatsAppIcon}
iconSize={20}
/>
<MenuItem
name="Signal"
href="/signal"
selected={pathname.startsWith("/signal")}
Icon={ChatIcon}
iconSize={20}
/>
<MenuItem
name="Facebook"
href="/facebook"
selected={pathname.startsWith("/facebook")}
Icon={FacebookIcon}
iconSize={20}
/>
<MenuItem
name="Voice"
href="/voice"
selected={pathname.startsWith("/voice")}
Icon={PhoneIcon}
iconSize={20}
/>
<MenuItem
name="Users"
href="/users"
selected={pathname.startsWith("/users")}
Icon={AccountCircleIcon}
iconSize={20}
/>
</List>
</Grid>
</Grid>
</Drawer>
);
};

View file

@ -0,0 +1,135 @@
"use client";
import { FC } from "react";
import {
Box,
Typography,
ListItemButton,
ListItemIcon,
ListItemText,
ListItemSecondaryAction,
} from "@mui/material";
import Link from "next/link";
import Image from "next/image";
import { fonts } from "../styles/theme";
const openWidth = 270;
const closedWidth = 100;
export const SidebarItem: FC = ({
name,
href,
Icon,
iconSize,
inset = false,
selected = false,
open = true,
badge,
target = "_self",
}: any) => (
<Link href={href} target={target}>
<ListItemButton
sx={{
p: 0,
mb: 1,
bl: iconSize === 0 ? "1px solid white" : "inherit",
}}
selected={selected}
>
{iconSize > 0 ? (
<ListItemIcon
sx={{
color: `white`,
minWidth: 0,
mr: 2,
textAlign: "center",
margin: open ? "0 8 0 0" : "0 auto",
}}
>
<Box
sx={{
width: iconSize,
height: iconSize,
mr: 0.5,
mt: "-4px",
}}
>
<Icon />
</Box>
</ListItemIcon>
) : (
<Box
sx={{
width: 30,
height: "28px",
position: "relative",
ml: "9px",
mr: "1px",
}}
>
<Box
sx={{
width: "1px",
height: "56px",
backgroundColor: "white",
position: "absolute",
left: "3px",
top: "-10px",
}}
/>
<Box
sx={{
width: "42px",
height: "42px",
position: "absolute",
top: "-27px",
left: "3px",
border: "solid 1px #fff",
borderColor: "transparent transparent transparent #fff",
borderRadius: "60px",
rotate: "-35deg",
}}
/>
</Box>
)}
{open && (
<ListItemText
inset={inset}
primary={
<Typography
variant="body1"
sx={{
fontSize: 16,
fontWeight: "bold",
border: 0,
textAlign: "left",
color: "white",
}}
>
{name}
</Typography>
}
/>
)}
{badge && badge > 0 ? (
<ListItemSecondaryAction>
<Typography
color="textSecondary"
variant="body1"
className="badge"
sx={{
backgroundColor: "#FFB620",
color: "black !important",
borderRadius: 10,
px: 1,
fontSize: 12,
fontWeight: "bold",
}}
>
{badge}
</Typography>
</ListItemSecondaryAction>
) : null}
</ListItemButton>
</Link>
);

2
packages/ui/index.ts Normal file
View file

@ -0,0 +1,2 @@
export { List } from "./components/List";
export { fonts, typography, colors } from "./styles/theme";

25
packages/ui/package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "ui",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "tsc -p tsconfig.json"
},
"author": "",
"license": "ISC",
"dependencies": {
"@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.168",
"@mui/material": "^5",
"@mui/x-data-grid-pro": "^6.19.6",
"@mui/x-date-pickers-pro": "^6.19.7",
"next": "14.1.3",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "^20.11.28",
"@types/react": "18.2.66",
"typescript": "^5.4.2"
}
}

112
packages/ui/styles/theme.ts Normal file
View file

@ -0,0 +1,112 @@
import { Roboto, Playfair_Display, Poppins } from "next/font/google";
const roboto = Roboto({
weight: ["400"],
subsets: ["latin"],
display: "swap",
});
const playfair = Playfair_Display({
weight: ["900"],
subsets: ["latin"],
display: "swap",
});
const poppins = Poppins({
weight: ["400", "700"],
subsets: ["latin"],
display: "swap",
});
export const fonts = {
roboto,
playfair,
poppins,
};
export const colors: any = {
lightGray: "#ededf0",
mediumGray: "#e3e5e5",
darkGray: "#33302f",
mediumBlue: "#4285f4",
green: "#349d7b",
lavender: "#a5a6f6",
darkLavender: "#5d5fef",
pink: "#fcddec",
cdrLinkOrange: "#ff7115",
coreYellow: "#fac942",
helpYellow: "#fff4d5",
dwcDarkBlue: "#191847",
hazyMint: "#ecf7f8",
leafcutterElectricBlue: "#4d6aff",
leafcutterLightBlue: "#fafbfd",
waterbearElectricPurple: "#332c83",
waterbearLightSmokePurple: "#eff3f8",
bumpedPurple: "#212058",
mutedPurple: "#373669",
warningPink: "#ef5da8",
lightPink: "#fff0f7",
lightGreen: "#f0fff3",
lightOrange: "#fff5f0",
beige: "#f6f2f1",
almostBlack: "#33302f",
white: "#ffffff",
};
export const typography: any = {
h1: {
fontFamily: playfair.style.fontFamily,
fontSize: 45,
fontWeight: 700,
lineHeight: 1.1,
margin: 0,
},
h2: {
fontFamily: poppins.style.fontFamily,
fontSize: 35,
fontWeight: 700,
lineHeight: 1.1,
margin: 0,
},
h3: {
fontFamily: poppins.style.fontFamily,
fontWeight: 400,
fontSize: 27,
lineHeight: 1.1,
margin: 0,
},
h4: {
fontFamily: poppins.style.fontFamily,
fontWeight: 700,
fontSize: 18,
},
h5: {
fontFamily: roboto.style.fontFamily,
fontWeight: 700,
fontSize: 16,
lineHeight: "24px",
textTransform: "uppercase",
textAlign: "center",
margin: 1,
},
h6: {
fontFamily: roboto.style.fontFamily,
fontWeight: 400,
fontSize: 14,
textAlign: "center",
},
p: {
fontFamily: roboto.style.fontFamily,
fontSize: 17,
lineHeight: "26.35px",
fontWeight: 400,
margin: 0,
},
small: {
fontFamily: roboto.style.fontFamily,
fontSize: 13,
lineHeight: "18px",
fontWeight: 400,
margin: 0,
},
};

29
packages/ui/tsconfig.json Normal file
View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*", "../../node_modules/*"]
},
"plugins": [
{
"name": "next"
}
]
},
"include": ["**.d.ts", "**/*.ts", "**/*.tsx", "**/*.png, **/*.svg"],
"exclude": ["node_modules", "babel__core"]
}

File diff suppressed because one or more lines are too long