diff --git a/.github/workflows/deploy-images-on-release.yml b/.github/workflows/deploy-images-on-release.yml index f1d0dd7ce..cf06f7840 100644 --- a/.github/workflows/deploy-images-on-release.yml +++ b/.github/workflows/deploy-images-on-release.yml @@ -11,6 +11,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Extract version from tag id: extract_tag run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" @@ -93,6 +96,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -119,12 +124,16 @@ jobs: platforms: linux/amd64,linux/arm64 labels: | org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate + build-args: | + VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }} docker-build-and-push-server-mono: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Extract version id: extract_tag @@ -143,6 +152,7 @@ jobs: -t ghcr.io/bluewave-labs/checkmate-backend-mono:${{ steps.extract_tag.outputs.version }} \ -f ./docker/dist-mono/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }} \ . - name: Push Server Docker image diff --git a/.github/workflows/deploy-images.yml b/.github/workflows/deploy-images.yml index d3d70b90b..fa39fed85 100644 --- a/.github/workflows/deploy-images.yml +++ b/.github/workflows/deploy-images.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate-client:latest \ -f ./docker/dist/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image @@ -85,6 +91,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -96,6 +104,10 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: Build and push multi-arch Docker image uses: docker/build-push-action@v5 with: @@ -107,12 +119,16 @@ jobs: platforms: linux/amd64,linux/arm64 labels: | org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate + build-args: | + VITE_APP_VERSION=${{ env.VERSION }} docker-build-and-push-server-mono: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -121,12 +137,17 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: Build Server Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate-backend-mono:latest \ -f ./docker/dist-mono/server.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Server Docker image diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml index 6956f5caa..7764c488b 100644 --- a/.github/workflows/production-deploy.yml +++ b/.github/workflows/production-deploy.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate:frontend-demo \ -f ./docker/prod/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml index 34fa243c6..ea5477815 100644 --- a/.github/workflows/staging-deploy.yml +++ b/.github/workflows/staging-deploy.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -18,12 +20,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get version + id: vars + run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Build Client Docker image run: | docker build \ -t ghcr.io/bluewave-labs/checkmate:frontend-staging \ -f ./docker/staging/client.Dockerfile \ --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \ + --build-arg VITE_APP_VERSION=${{ env.VERSION }} \ . - name: Push Client Docker image diff --git a/client/src/Components/RoleProtectedRoute/index.jsx b/client/src/Components/RoleProtectedRoute/index.jsx new file mode 100644 index 000000000..c7b7dfc12 --- /dev/null +++ b/client/src/Components/RoleProtectedRoute/index.jsx @@ -0,0 +1,35 @@ +import { Navigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import PropTypes from "prop-types"; + +/** + * ProtectedRoute is a wrapper component that ensures only authenticated users + * can access the wrapped content. It checks authentication status (e.g., from Redux or Context). + * If the user is authenticated, it renders the children; otherwise, it redirects to the login page. + * + * @param {Object} props - The props passed to the ProtectedRoute component. + * @param {React.ReactNode} props.children - The children to render if the user is authenticated. + * @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page. + */ + +const RoleProtectedRoute = ({ roles, children }) => { + const authState = useSelector((state) => state.auth); + const userRoles = authState?.user?.role || []; + const canAccess = userRoles.some((role) => roles.includes(role)); + + return canAccess ? ( + children + ) : ( + + ); +}; + +RoleProtectedRoute.propTypes = { + children: PropTypes.element.isRequired, + roles: PropTypes.array, +}; + +export default RoleProtectedRoute; diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx index e9f0d3ad7..219ceff1e 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/Sidebar/index.jsx @@ -49,6 +49,8 @@ import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { clearAuthState } from "../../Features/Auth/authSlice"; import { toggleSidebar } from "../../Features/UI/uiSlice"; +import { TurnedIn } from "@mui/icons-material"; +import { rules } from "eslint-plugin-react-refresh"; const getMenu = (t) => [ { name: t("menu.uptime"), path: "uptime", icon: }, @@ -121,7 +123,6 @@ function Sidebar() { const { t } = useTranslation(); const authState = useSelector((state) => state.auth); - const menu = getMenu(t); const otherMenuItems = getOtherMenuItems(t); const accountMenuItems = getAccountMenuItems(t); const collapsed = useSelector((state) => state.ui.sidebar.collapsed); @@ -133,6 +134,13 @@ function Sidebar() { const sidebarRef = useRef(null); const [sidebarReady, setSidebarReady] = useState(false); const TRANSITION_DURATION = 200; + let menu = getMenu(t); + menu = menu.filter((item) => { + if (item.path === "logs") { + return user.role?.includes("admin") || user.role?.includes("superadmin"); + } + return true; + }); useEffect(() => { if (!collapsed) { diff --git a/client/src/Components/TabPanels/Account/TeamPanel.jsx b/client/src/Components/TabPanels/Account/TeamPanel.jsx index 341f84ba7..45578cc6c 100644 --- a/client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -168,7 +168,7 @@ const TeamPanel = () => { }); } catch (error) { createToast({ - body: error.message || "Unknown error.", + body: error?.response?.data?.msg || error.message || "Unknown error.", }); } finally { setIsSendingInvite(false); @@ -268,7 +268,7 @@ const TeamPanel = () => { value={toInvite.email} onChange={handleChange} error={errors.email ? true : false} - helperText={errors.email} + helperText={t(errors.email)} />