diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index 6c1f70fd7..a5d487dc8 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: client + name: checkmate-client spec: replicas: 1 selector: matchLabels: - app: client + app: checkmate-client template: metadata: labels: - app: client + app: checkmate-client spec: containers: - name: client @@ -20,3 +20,13 @@ spec: env: - name: UPTIME_APP_API_BASE_URL value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" + - name: UPTIME_APP_CLIENT_HOST + value: "http://{{ .Values.client.ingress.host }}" + volumeMounts: + - name: config-volume + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + volumes: + - name: config-volume + configMap: + name: checkmate-server-nginx-cm diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index da059a863..89e001e15 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -2,10 +2,15 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: client-ingress + name: checkmate-client-ingress + {{- if .Values.client.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / + {{- range $key, $value := .Values.client.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: + ingressClassName: {{ .Values.client.ingress.className }} rules: - host: {{ .Values.client.ingress.host }} http: @@ -14,7 +19,7 @@ spec: pathType: Prefix backend: service: - name: client + name: checkmate-client port: number: {{ .Values.client.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/client-service.yaml b/charts/helm/checkmate/templates/client-service.yaml index afb88188c..b56eaeedf 100644 --- a/charts/helm/checkmate/templates/client-service.yaml +++ b/charts/helm/checkmate/templates/client-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: client + name: checkmate-client spec: selector: - app: client + app: checkmate-client ports: - port: {{ .Values.client.port }} diff --git a/charts/helm/checkmate/templates/mongodb-deployment.yaml b/charts/helm/checkmate/templates/mongodb-deployment.yaml deleted file mode 100644 index 4c906fe09..000000000 --- a/charts/helm/checkmate/templates/mongodb-deployment.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mongodb -spec: - replicas: 1 - selector: - matchLabels: - app: mongodb - template: - metadata: - labels: - app: mongodb - spec: - containers: - - name: mongodb - image: {{ .Values.mongodb.image }} - ports: - - containerPort: {{ .Values.mongodb.port }} - command: ["mongod", "--quiet", "--bind_ip_all"] - volumeMounts: - - name: mongodb-data - mountPath: /data/db - volumes: - - name: mongodb-data - persistentVolumeClaim: - claimName: mongodb-pvc \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-pvc.yaml b/charts/helm/checkmate/templates/mongodb-pvc.yaml deleted file mode 100644 index 435b662e6..000000000 --- a/charts/helm/checkmate/templates/mongodb-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mongodb-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-service.yaml b/charts/helm/checkmate/templates/mongodb-service.yaml index 2911dc3eb..b96fe1a82 100644 --- a/charts/helm/checkmate/templates/mongodb-service.yaml +++ b/charts/helm/checkmate/templates/mongodb-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: mongodb + name: checkmate-mongodb spec: selector: - app: mongodb + app: checkmate-mongodb ports: - port: {{ .Values.mongodb.port }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml new file mode 100644 index 000000000..1ffd1c228 --- /dev/null +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: checkmate-mongodb +spec: + replicas: 1 + selector: + matchLabels: + app: checkmate-mongodb + template: + metadata: + labels: + app: checkmate-mongodb + spec: + containers: + - name: mongodb + image: {{ .Values.mongodb.image }} + ports: + - containerPort: {{ .Values.mongodb.port }} + command: ["mongod", "--quiet", "--bind_ip_all"] + volumeMounts: + - name: checkmate-mongo-persistent-storage + mountPath: /data/db + volumeClaimTemplates: + - metadata: + name: checkmate-mongo-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-deployment.yaml b/charts/helm/checkmate/templates/redis-deployment.yaml deleted file mode 100644 index cc9ffdaf9..000000000 --- a/charts/helm/checkmate/templates/redis-deployment.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: {{ .Values.redis.image }} - ports: - - containerPort: {{ .Values.redis.port }} - volumeMounts: - - name: redis-data - mountPath: /data - volumes: - - name: redis-data - persistentVolumeClaim: - claimName: redis-pvc diff --git a/charts/helm/checkmate/templates/redis-pvc.yaml b/charts/helm/checkmate/templates/redis-pvc.yaml deleted file mode 100644 index 2f0d81960..000000000 --- a/charts/helm/checkmate/templates/redis-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: redis-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.redisSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-service.yaml b/charts/helm/checkmate/templates/redis-service.yaml index f29fa46de..2720350da 100644 --- a/charts/helm/checkmate/templates/redis-service.yaml +++ b/charts/helm/checkmate/templates/redis-service.yaml @@ -1,9 +1,11 @@ +{{- if .Values.redis.enabled }} apiVersion: v1 kind: Service metadata: - name: redis + name: checkmate-redis spec: selector: - app: redis + app: checkmate-redis ports: - port: {{ .Values.redis.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/redis-statefulsets.yaml b/charts/helm/checkmate/templates/redis-statefulsets.yaml new file mode 100644 index 000000000..252361730 --- /dev/null +++ b/charts/helm/checkmate/templates/redis-statefulsets.yaml @@ -0,0 +1,33 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: checkmate-redis +spec: + replicas: 1 + selector: + matchLabels: + app: checkmate-redis + template: + metadata: + labels: + app: checkmate-redis + spec: + containers: + - name: redis + image: {{ .Values.redis.image }} + ports: + - containerPort: {{ .Values.redis.port }} + volumeMounts: + - name: redis-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: checkmate-redis-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.redisSize }} +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/secrets.yaml b/charts/helm/checkmate/templates/secrets.yaml index 0a3f1ace5..af104e7d1 100644 --- a/charts/helm/checkmate/templates/secrets.yaml +++ b/charts/helm/checkmate/templates/secrets.yaml @@ -4,10 +4,6 @@ {{- fail "secrets.JWT_SECRET must be overridden and cannot be 'change_me'" }} {{- end }} -{{- if or (not $secrets.REFRESH_TOKEN_SECRET) (eq $secrets.REFRESH_TOKEN_SECRET "change_me") }} - {{- fail "secrets.REFRESH_TOKEN_SECRET must be overridden and cannot be 'change_me'" }} -{{- end }} - apiVersion: v1 kind: Secret metadata: diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 6f21f86f3..8c9403916 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: server + name: checkmate-server spec: replicas: 1 selector: matchLabels: - app: server + app: checkmate-server template: metadata: labels: - app: server + app: checkmate-server spec: containers: - name: server @@ -20,3 +20,6 @@ spec: envFrom: - secretRef: name: checkmate-secrets + env: + - name: CLIENT_HOST + value: "http://{{ .Values.client.ingress.host }}" diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index c1982d451..0d7ca867b 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -2,15 +2,22 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: server-ingress + name: checkmate-server-ingress + {{- if .Values.server.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" - nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" - nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" + {{- range $key, $value := .Values.server.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{/*#annotations: + #nginx.ingress.kubernetes.io/rewrite-target: / + #nginx.ingress.kubernetes.io/enable-cors: "true" + #nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" + #nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" + #nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" + #nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}} spec: + ingressClassName: {{ .Values.server.ingress.className }} rules: - host: {{ .Values.server.ingress.host }} http: @@ -19,7 +26,7 @@ spec: pathType: Prefix backend: service: - name: server + name: checkmate-server port: number: {{ .Values.server.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/server-nginx-cm.yaml b/charts/helm/checkmate/templates/server-nginx-cm.yaml new file mode 100644 index 000000000..84da55299 --- /dev/null +++ b/charts/helm/checkmate/templates/server-nginx-cm.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: checkmate-server-nginx-cm +data: + default.conf: | + server { + listen 80; + listen [::]:80; + + server_name checkmate-demo.bluewavelabs.ca; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # location /api/ { + # proxy_pass http://{{ .Values.server.ingress.host }}:5000/api/; + # proxy_http_version 1.1; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # } + + location /api-docs/ { + proxy_pass http://{{ .Values.server.ingress.host }}:5000/api-docs/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-service.yaml b/charts/helm/checkmate/templates/server-service.yaml index 64494ff90..d8a7fbe86 100644 --- a/charts/helm/checkmate/templates/server-service.yaml +++ b/charts/helm/checkmate/templates/server-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: server + name: checkmate-server spec: selector: - app: server + app: checkmate-server ports: - port: {{ .Values.server.port }} diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index 79c77a49e..1840cf909 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -1,40 +1,46 @@ client: - image: ghcr.io/bluewave-labs/checkmate-client:v2.3 + image: ghcr.io/bluewave-labs/checkmate-client:v3.2.0 port: 80 ingress: enabled: true host: change_me + className: nginx + annotations: {} server: - image: ghcr.io/bluewave-labs/checkmate-backend:v2.3 + image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0 port: 52345 - protocol: change_me + protocol: http ingress: enabled: true host: change_me + className: nginx + annotations: {} redis: + enabled: false image: redis:7.2 port: 6379 mongodb: - image: mongo:7.0 + image: ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0 port: 27017 secrets: JWT_SECRET: change_me - REFRESH_TOKEN_SECRET: change_me - SYSTEM_EMAIL_ADDRESS: test@example.com - SYSTEM_EMAIL_PASSWORD: change_me - SYSTEM_EMAIL_HOST: smtp.example.com - SYSTEM_EMAIL_PORT: "587" - PAGESPEED_API_KEY: change_me - DB_CONNECTION_STRING: mongodb://mongodb:27017/uptime_db - REDIS_HOST: redis - REDIS_PORT: "6379" - DB_TYPE: MongoDB - TOKEN_TTL: 99d - REFRESH_TOKEN_TTL: 99d +# REFRESH_TOKEN_SECRET: change_me +# SYSTEM_EMAIL_ADDRESS: test@example.com +# SYSTEM_EMAIL_PASSWORD: change_me +# SYSTEM_EMAIL_HOST: smtp.example.com +# SYSTEM_EMAIL_PORT: "587" +# PAGESPEED_API_KEY: change_me + DB_CONNECTION_STRING: mongodb://checkmate-mongodb.namespace.svc:27017/uptime_db + CLIENT_HOST: change_me +# REDIS_HOST: redis +# REDIS_PORT: "6379" +# DB_TYPE: MongoDB +# TOKEN_TTL: 99d +# REFRESH_TOKEN_TTL: 99d persistence: mongodbSize: 5Gi diff --git a/client/src/Components/v2/Arrows/ArrowLeft.tsx b/client/src/Components/v2/Arrows/ArrowLeft.tsx new file mode 100644 index 000000000..01d1342c8 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowLeft.tsx @@ -0,0 +1,36 @@ +import LeftArrow from "@/assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react"; + +export const ArrowLeft = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else if (type === "long") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Arrows/ArrowRight.tsx b/client/src/Components/v2/Arrows/ArrowRight.tsx new file mode 100644 index 000000000..c73165f16 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowRight.tsx @@ -0,0 +1,28 @@ +import RightArrow from "@/assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react"; + +export const ArrowRight = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index be2f42e30..dbc6f2e5e 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -1,39 +1,6 @@ -import { useState, useEffect } from "react"; -import useMediaQuery from "@mui/material/useMediaQuery"; -import { useTheme } from "@mui/material/styles"; - import { Outlet } from "react-router"; import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; - -const COLLAPSED_WIDTH = 50; -const EXPANDED_WIDTH = 250; - -const SideBar = () => { - const theme = useTheme(); - const isSmall = useMediaQuery(theme.breakpoints.down("md")); - - const [collapsed, setCollapsed] = useState(false); - useEffect(() => { - setCollapsed(isSmall); - }, [isSmall]); - return ( - - setCollapsed(!collapsed)} - > - Sidebar Content - - - ); -}; +import { SideBar } from "@/Components/v2/Layouts/Sidebar"; const RootLayout = () => { return ( diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx new file mode 100644 index 000000000..a07ccde6c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx @@ -0,0 +1,16 @@ +import { useTheme } from "@mui/material/styles"; + +import Stack from "@mui/material/Stack"; + +export const BottomControls = ({}) => { + const theme = useTheme(); + return ( + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx new file mode 100644 index 000000000..564c41591 --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx @@ -0,0 +1,50 @@ +import IconButton from "@mui/material/IconButton"; +import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight"; +import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft"; +import { useTheme } from "@mui/material/styles"; +import { useDispatch } from "react-redux"; +import { toggleSidebar } from "../../../../Features/UI/uiSlice.js"; + +export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const arrowIcon = collapsed ? ( + + ) : ( + + ); + + return ( + { + dispatch(toggleSidebar()); + }} + > + {arrowIcon} + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx new file mode 100644 index 000000000..6d190b99d --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx @@ -0,0 +1,59 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; + +export const Logo = ({ collapsed }: { collapsed: boolean }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {" "} + + {t("common.appName")} + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx new file mode 100644 index 000000000..5b3ed705e --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -0,0 +1,58 @@ +import Notifications from "@/assets/icons/notifications.svg?react"; +import Monitors from "@/assets/icons/monitors.svg?react"; +import PageSpeed from "@/assets/icons/page-speed.svg?react"; +import Integrations from "@/assets/icons/integrations.svg?react"; +import Incidents from "@/assets/icons/incidents.svg?react"; +import StatusPages from "@/assets/icons/status-pages.svg?react"; +import Maintenance from "@/assets/icons/maintenance.svg?react"; +import Logs from "@/assets/icons/logs.svg?react"; +import Settings from "@/assets/icons/settings.svg?react"; +import Support from "@/assets/icons/support.svg?react"; +import Discussions from "@/assets/icons/discussions.svg?react"; +import Docs from "@/assets/icons/docs.svg?react"; +import ChangeLog from "@/assets/icons/changeLog.svg?react"; + +export const getMenu = (t: Function) => [ + { name: t("menu.uptime"), path: "v2/uptime", icon: }, + { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + + { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + { + name: t("menu.notifications"), + path: "notifications", + icon: , + }, + { name: t("menu.incidents"), path: "incidents", icon: }, + + { name: t("menu.statusPages"), path: "status", icon: }, + { name: t("menu.maintenance"), path: "maintenance", icon: }, + { name: t("menu.logs"), path: "logs", icon: }, + + { + name: t("menu.settings"), + icon: , + path: "settings", + }, +]; + +export const getBottomMenu = (t: Function) => [ + { name: t("menu.support"), path: "support", icon: , url: "invite" }, + { + name: t("menu.discussions"), + path: "discussions", + icon: , + url: "https://github.com/bluewave-labs/checkmate/discussions", + }, + { + name: t("menu.docs"), + path: "docs", + icon: , + url: "https://bluewavelabs.gitbook.io/checkmate", + }, + { + name: t("menu.changelog"), + path: "changelog", + icon: , + url: "https://github.com/bluewave-labs/checkmate/releases", + }, +]; diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx new file mode 100644 index 000000000..8f7f5d68c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx @@ -0,0 +1,103 @@ +import Tooltip from "@mui/material/Tooltip"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; + +export interface NavData { + name: string; + icon: JSX.Element; +} + +export const NavItem = ({ + item, + collapsed, + selected, + onClick, +}: { + item: NavData; + collapsed: boolean; + selected: boolean; + onClick: (event: React.MouseEvent) => void; +}) => { + const theme = useTheme(); + const iconStroke = selected + ? theme.palette.primary.contrastText + : theme.palette.primary.contrastTextTertiary; + + const buttonBgColor = selected ? theme.palette.secondary.main : "transparent"; + const buttonBgHoverColor = selected + ? theme.palette.secondary.main + : theme.palette.tertiary.main; + const fontWeight = selected ? 600 : 400; + return ( + + + + {item.icon} + + + + {item.name} + + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx new file mode 100644 index 000000000..b05716aee --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -0,0 +1,103 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setCollapsed } from "@/Features/UI/uiSlice"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useLocation } from "react-router-dom"; + +import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton"; +import Stack from "@mui/material/Stack"; +import List from "@mui/material/List"; +import Divider from "@mui/material/Divider"; +import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo"; +import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu"; +import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem"; +import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls"; + +export const COLLAPSED_WIDTH = 64; +export const EXPANDED_WIDTH = 250; + +export const SideBar = () => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const dispatch = useDispatch(); + const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed); + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + const menu = getMenu(t); + const bottomMenu = getBottomMenu(t); + + useEffect(() => { + dispatch(setCollapsed({ collapsed: isSmall })); + }, [isSmall]); + + return ( + + + + + {menu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + return ( + navigate(`/${item.path}`)} + /> + ); + })} + + + {bottomMenu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + + return ( + { + if (item.url) { + window.open(item.url, "_blank", "noreferrer"); + } else { + navigate(`/${item.path}`); + } + }} + /> + ); + })} + + + + + ); +}; diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts new file mode 100644 index 000000000..28a18b659 --- /dev/null +++ b/client/src/Features/Auth/v2AuthSlice.ts @@ -0,0 +1,19 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + isAuthenticated: false, +}; + +const v2AuthSlice = createSlice({ + name: "v2Auth", + initialState, + reducers: { + setIsAuthenticated: (state, action) => { + const { authenticated } = action.payload; + state.isAuthenticated = authenticated; + }, + }, +}); + +export default v2AuthSlice.reducer; +export const { setIsAuthenticated } = v2AuthSlice.actions; diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js index 45ba267b0..63f7ae90e 100644 --- a/client/src/Features/UI/uiSlice.js +++ b/client/src/Features/UI/uiSlice.js @@ -47,6 +47,10 @@ const uiSlice = createSlice({ toggleSidebar: (state) => { state.sidebar.collapsed = !state.sidebar.collapsed; }, + setCollapsed: (state, action) => { + const { collapsed } = action.payload; + state.sidebar.collapsed = collapsed; + }, setMode: (state, action) => { state.mode = action.payload; }, @@ -73,6 +77,7 @@ export default uiSlice.reducer; export const { setRowsPerPage, toggleSidebar, + setCollapsed, setMode, setShowURL, setGreeting, diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index fd4a08e93..a41750c27 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -3,7 +3,9 @@ import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; - +import { useDispatch } from "react-redux"; +import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice"; +import { useNavigate } from "react-router"; import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; @@ -19,8 +21,10 @@ type FormData = z.infer; const Login = () => { const { t } = useTranslation(); + const dispatch = useDispatch(); const theme = useTheme(); - const { post, loading, error } = usePost("/auth/login"); + const { post, loading } = usePost("/auth/login"); + const navigate = useNavigate(); const { handleSubmit, @@ -37,9 +41,10 @@ const Login = () => { const onSubmit = async (data: FormData) => { const result = await post(data); if (result) { - console.log(result.message); + dispatch(setIsAuthenticated({ authenticated: true })); + navigate("/v2/uptime"); } else { - console.error("Login failed:", error); + dispatch(setIsAuthenticated({ authenticated: false })); } }; diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx index 941550026..12d3eb595 100644 --- a/client/src/Routes/v2router.tsx +++ b/client/src/Routes/v2router.tsx @@ -25,7 +25,11 @@ const V2Routes = ({ mode = "light" }) => { element={} > Uptime} + /> + Test Page} /> diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts index 2875f750d..855f7b6b8 100644 --- a/client/src/Utils/Theme/v2/palette.ts +++ b/client/src/Utils/Theme/v2/palette.ts @@ -54,12 +54,18 @@ export const lightPalette = { main: colors.offWhite, contrastText: colors.blueGray800, contrastTextSecondary: colors.blueGray600, + contrastTextTertiary: colors.blueGray500, + lowContrast: colors.gray250, }, secondary: { main: colors.gray200, light: colors.lightBlueWave, contrastText: colors.blueGray600, }, + tertiary: { + main: colors.gray100, + contrastText: colors.blueGray800, + }, }; export const darkPalette = { @@ -73,10 +79,16 @@ export const darkPalette = { main: colors.offBlack, contrastText: colors.blueGray50, contrastTextSecondary: colors.gray200, + contrastTextTertiary: colors.gray500, + lowContrast: colors.blueGray600, }, secondary: { main: "#313131", light: colors.lightBlueWave, contrastText: colors.gray200, }, + tertiary: { + main: colors.blueGray800, + contrastText: colors.gray100, + }, }; diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts index 55d2b9bb9..77bab3e70 100644 --- a/client/src/Utils/Theme/v2/theme.ts +++ b/client/src/Utils/Theme/v2/theme.ts @@ -67,6 +67,9 @@ export const theme = (mode: string, palette: any) => }, }, }, + shape: { + borderRadius: 2, + }, }); export const lightTheme = createTheme(theme("light", lightPalette)); diff --git a/client/src/store.js b/client/src/store.js index 69aa3d50b..565d68519 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -1,6 +1,7 @@ import { configureStore, combineReducers } from "@reduxjs/toolkit"; import authReducer from "./Features/Auth/authSlice"; +import v2AuthReducer from "./Features/Auth/v2AuthSlice"; import uiReducer from "./Features/UI/uiSlice"; import storage from "redux-persist/lib/storage"; import { persistReducer, persistStore, createTransform } from "redux-persist"; @@ -19,12 +20,13 @@ const authTransform = createTransform( const persistConfig = { key: "root", storage, - whitelist: ["auth", "ui"], + whitelist: ["auth", "v2Auth", "ui"], transforms: [authTransform], }; const rootReducer = combineReducers({ auth: authReducer, + v2Auth: v2AuthReducer, ui: uiReducer, }); diff --git a/client/src/types/env.d.ts b/client/src/types/env.d.ts index e2bce23dd..74a2a29e7 100644 --- a/client/src/types/env.d.ts +++ b/client/src/types/env.d.ts @@ -7,3 +7,11 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +declare module "*.svg?react" { + import * as React from "react"; + const ReactComponent: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + export default ReactComponent; +} diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts index 851ffea70..84bcd799f 100644 --- a/client/src/types/mui.d.ts +++ b/client/src/types/mui.d.ts @@ -3,13 +3,17 @@ import "@mui/material/Button"; declare module "@mui/material/styles" { interface Palette { accent: Palette["primary"]; + tertiary: Palette["primary"]; } interface PaletteOptions { accent?: PaletteOptions["primary"]; + tertiary?: PaletteOptions["primary"]; } interface PaletteColor { contrastTextSecondary?: string; + contrastTextTertiary?: string; + lowContrast?: string; } }