Compare commits

...

16 Commits

Author SHA1 Message Date
Chris Rai
cb590af8d7 add keycloak test user creds for mini cluster 2026-02-04 21:21:24 -05:00
Chris Rai
1209e50c58 preprod4 image 2026-02-04 20:28:32 -05:00
Chris Rai
2fb8f5ec6a Update config 2026-02-04 20:17:16 -05:00
Chris Rai
0a8b0b0120 Point to preprod endpoints 2026-02-04 20:06:18 -05:00
Chris Rai
1c55b0b562 Fix Keycloak auth: optional chaining for useAuth hook 2026-01-31 23:42:53 -05:00
Chris Rai
c0917724f2 Use localhost:32000 registry for mini cluster 2026-01-31 22:36:20 -05:00
Chris Rai
242ddecfa4 Use local registry for mini cluster 2026-01-31 22:24:11 -05:00
Chris Rai
5a682a9618 Add Keycloak OIDC authentication alongside Cognito 2026-01-31 22:09:36 -05:00
Chris Rai
3bdff103aa Point OTA service to local gateway 2026-01-31 17:37:04 -05:00
Chris Rai
9d5786cda8 bump memory request to match limit 2026-01-14 21:08:24 -05:00
Chris Rai
e63b3aa56f Add imagePullPolicy Never for local image 2026-01-13 22:55:48 -05:00
Chris Rai
0be80ebb4a Use external Gitea registry URL 2026-01-13 21:42:54 -05:00
Chris Rai
a4f3f5abf8 Use internal Gitea registry URL 2026-01-13 21:40:15 -05:00
Chris Rai
184a5b4e0f Fix build script, update README with build instructions 2026-01-13 21:31:18 -05:00
Chris Rai
48a8d13580 Remove .vscode 2026-01-13 21:26:00 -05:00
Chris Rai
d2c361de4a Clean up for mini: remove other envs, add devbox/direnv 2026-01-13 21:24:46 -05:00
34 changed files with 356 additions and 1089 deletions

View File

@@ -1,23 +0,0 @@
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cec-euprd.fiskerinc.com
REACT_APP_AUTH_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/certificate
REACT_APP_ENV=cec-euprd
REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=https://gw.cec-euprd.fiskerinc.com/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://superset.cec-euprd.fiskerinc.com
REACT_APP_ROLE_CREATE=e92f2b3e-1b80-42e5-9483-8ae648224dc6
REACT_APP_ROLE_READ_ONLY=ae5123e3-7a6a-4ca9-947b-a0f184f82cbd
REACT_APP_ROLE_DELETE=bfd1cccc-213a-4f31-b3d1-6e685976aec8
REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
REACT_APP_ECCKEY_ENV=
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":49.8327,"lng":9.8816,"zoom":4.5}
REACT_APP_ENABLE_DEBUGMASK=1
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=0

View File

@@ -1,23 +0,0 @@
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cec-prd.fiskerinc.com
REACT_APP_AUTH_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/certificate
REACT_APP_ENV=cec-prd
REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://superset.cec-prd.fiskerinc.com
REACT_APP_ROLE_CREATE=e92f2b3e-1b80-42e5-9483-8ae648224dc6
REACT_APP_ROLE_READ_ONLY=ae5123e3-7a6a-4ca9-947b-a0f184f82cbd
REACT_APP_ROLE_DELETE=bfd1cccc-213a-4f31-b3d1-6e685976aec8
REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
REACT_APP_ECCKEY_ENV=
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
REACT_APP_ENABLE_DEBUGMASK=1
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=1

View File

@@ -1,23 +0,0 @@
REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com
REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate
REACT_APP_ENV=dev
REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com
REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
REACT_APP_ECCKEY_ENV=stage,prod
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
REACT_APP_ENABLE_DEBUGMASK=1
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=1

View File

@@ -2,6 +2,12 @@ REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
REACT_APP_ENV=local REACT_APP_ENV=local
# Keycloak OIDC
REACT_APP_KEYCLOAK_ENABLED=true
REACT_APP_KEYCLOAK_URL=https://keycloak.mini.cloud.fiskerinc.com
REACT_APP_KEYCLOAK_REALM=compute-auth
REACT_APP_KEYCLOAK_CLIENT_ID=ota-portal
REACT_APP_MAGNA_PROVIDER=Magna REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=http://localhost/ota_update REACT_APP_OTA_SERVICE_URL=http://localhost/ota_update

View File

@@ -1,13 +1,20 @@
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.mini.cloud.fiskerinc.com REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.mini.cloud.fiskerinc.com
REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth REACT_APP_AUTH_SERVICE_URL=https://gw.cloud.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate
REACT_APP_ENV=mini REACT_APP_ENV=mini
# Keycloak OIDC (set to true to enable)
REACT_APP_KEYCLOAK_ENABLED=true
REACT_APP_KEYCLOAK_URL=https://keycloak.mini.cloud.fiskerinc.com
REACT_APP_KEYCLOAK_REALM=compute-auth
REACT_APP_KEYCLOAK_CLIENT_ID=ota-portal
REACT_APP_MAGNA_PROVIDER=Magna REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update REACT_APP_OTA_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com
REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701 REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
@@ -16,8 +23,8 @@ REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1 REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
REACT_APP_ECCKEY_ENV=stage,prod REACT_APP_ECCKEY_ENV=stage
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5} REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
REACT_APP_ENABLE_DEBUGMASK=1 REACT_APP_ENABLE_DEBUGMASK=1
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=1 REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=0

View File

@@ -1,23 +0,0 @@
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com
REACT_APP_AUTH_SERVICE_URL=https://gw.cloud.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate
REACT_APP_ENV=prd
REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe
REACT_APP_OTA_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com
REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
REACT_APP_ECCKEY_ENV=stage
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
REACT_APP_ENABLE_DEBUGMASK=1
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=0

View File

@@ -1,22 +0,0 @@
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
REACT_APP_ENV=local
REACT_APP_MAGNA_PROVIDER=DISABLED
REACT_APP_MAGNA_GROUP_ID=DISABLED
REACT_APP_OTA_SERVICE_URL=http://localhost/ota_update
REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll
REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll
REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com
REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
REACT_APP_ECCKEY_ENV=dev,stage,prod
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
REACT_APP_SHOW_AFTERSALES_EU_CERT_BUTTON=0

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use devbox

7
.github/CODEOWNERS vendored
View File

@@ -1,7 +0,0 @@
# default codeowners
* @Fisker-Inc/cloud
# devops
.github @Fisker-Inc/devops @Fisker-Inc/cloud
k8s @Fisker-Inc/devops @Fisker-Inc/cloud
Dockerfile @Fisker-Inc/devops @Fisker-Inc/cloud

View File

@@ -1,38 +0,0 @@
name: Blackduck Intelligent Scan
on:
schedule:
# run scans twice a month
- cron: '0 2 1,15 * *'
jobs:
blackduck:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm install
- run: npm run build
- name: Run Synopsys Detect INTELLIGENT
run: |
bash <(curl -s -L https://detect.synopsys.com/detect9.sh) \
--blackduck.url=${{ secrets.BLACKDUCK_URL }} \
--blackduck.api.token=${{ secrets.BLACKDUCK_API_KEY }} \
--blackduck.trust.cert=true \
--detect.project.version.update=true \
--detect.project.name='ota-admin-portal' \
--detect.excluded.directories='node_modules' \
--detect.project.version.name=$GITHUB_REF_NAME \
--detect.blackduck.scan.mode="INTELLIGENT" \
--detect.detector.search.depth=3 \
--detect.detector.search.continue=true \
--detect.npm.include.dev.dependencies=false
# --detect.policy.check.fail.on.severities=ALL,NONE,UNSPECIFIED,TRIVIAL,MINOR,MAJOR,CRITICAL,BLOCKER - Use it if you want to fail the build on a certain severity type
# --detect.detector.search.continue=true - If true, the bom tool search will continue to look for nested bom tools of the same type to the maximum search depth

View File

@@ -1,41 +0,0 @@
name: Blackduck Rapid scan
on:
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
jobs:
blackduck:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm install
- run: npm run build
- name: Run Synopsys Detect RAPID
run: |
bash <(curl -s -L https://detect.synopsys.com/detect9.sh) \
--blackduck.url=${{ secrets.BLACKDUCK_URL }} \
--blackduck.api.token=${{ secrets.BLACKDUCK_API_KEY }} \
--blackduck.trust.cert=true \
--detect.project.version.update=true \
--detect.project.name='ota-admin-portal' \
--detect.excluded.directories='node_modules' \
--detect.project.version.name=$GITHUB_REF_NAME \
--detect.blackduck.scan.mode="RAPID" \
--detect.detector.search.depth=3 \
--detect.detector.search.continue=true \
--detect.npm.include.dev.dependencies=false
# --detect.detector.search.continue=true - If true, the bom tool search will continue to look for nested bom tools of the same type to the maximum search depth

View File

@@ -1,135 +0,0 @@
name: OTA Admin Portal v2 Deploy - On Demand
on:
workflow_dispatch:
inputs:
environment:
description: "Environment"
required: true
type: choice
options:
- dev
- preprod
- cec-prd
- cec-euprd
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_CHANNEL: "#cloud-builds"
SLACK_FOOTER: ""
SLACK_USERNAME: GitHub Actions
SLACK_ICON: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
JFROG_NPMRC: ${{ secrets.JFROG_NPMRC }}
TAG: ${{ github.sha }}
PROJECT: ota-admin-portal-v2
REGISTRY: fiskercloud.azurecr.io
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: JFrog Auth
run: echo ${JFROG_NPMRC} | base64 -d > .npmrc
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=${{ inputs.environment }}
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} in ${{ inputs.environment }}! :this-is-fine:"
deploy:
needs: build
runs-on: arc-azure-${{ inputs.environment }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to ${{ inputs.environment }}... :partydeploy:"
- name: Set env
run: |
case ${{ inputs.environment }} in
dev)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_DEV }};;
preprod)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_PREPROD }};;
cec-prd)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_CEC_PRD }};;
cec-euprd)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_CEC_EUPRD }};;
*)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_DEV }};;
esac
echo "KUBECONFIG=${KUBECONFIG}" >> $GITHUB_ENV
- name: Deploy to env
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ env.KUBECONFIG }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG \
--wait -i -f k8s/values-${{ inputs.environment }}.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} on ${{ inputs.environment }}! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to ${{ inputs.environment }}! :gopher_party:"

View File

@@ -1,131 +0,0 @@
name: OTA Portal Deploy - On Demand
on:
workflow_dispatch:
inputs:
environment:
description: "Environment"
required: true
type: choice
options:
- dev
- preprod
- cec-prd
- cec-euprd
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_CHANNEL: "#cloud-builds"
SLACK_FOOTER: ""
SLACK_USERNAME: GitHub Actions
SLACK_ICON: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
TAG: ${{ github.sha }}
PROJECT: ota-admin-portal
REGISTRY: fiskercloud.azurecr.io
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=${{ inputs.environment }}
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-${{ inputs.environment }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} in ${{ inputs.environment }}! :this-is-fine:"
deploy:
needs: build
runs-on: arc-azure-${{ inputs.environment }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to ${{ inputs.environment }}... :partydeploy:"
- name: Set env
run: |
case ${{ inputs.environment }} in
dev)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_DEV }};;
preprod)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_PREPROD }};;
cec-prd)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_CEC_PRD }};;
cec-euprd)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_CEC_EUPRD }};;
*)
KUBECONFIG=${{ secrets.KUBECONFIG_AZURE_DEV }};;
esac
echo "KUBECONFIG=${KUBECONFIG}" >> $GITHUB_ENV
- name: Deploy to env
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ env.KUBECONFIG }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG-${{ inputs.environment }} \
--wait -i -f k8s/values-${{ inputs.environment }}.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} to ${{ inputs.environment }}! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to ${{ inputs.environment }}! :gopher_party:"

View File

@@ -1,371 +0,0 @@
name: OTA Portal Deploy
on:
push:
branches:
- main
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_CHANNEL: "#cloud-builds"
SLACK_FOOTER: ""
SLACK_USERNAME: GitHub Actions
SLACK_ICON: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
TAG: ${{ github.sha }}
PROJECT: ota-admin-portal
REGISTRY: fiskercloud.azurecr.io
permissions:
id-token: write
contents: read
jobs:
build-dev:
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push DEV
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=dev
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-dev
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} dev! :this-is-fine:"
deploy-dev:
needs: [build-dev]
runs-on: [ arc-azure-dev ]
environment: dev
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to dev... :partydeploy:"
- name: Deploy to dev
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBECONFIG_AZURE_DEV }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG-dev \
--wait -i -f k8s/values-dev.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} on dev! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to dev! :gopher_party:"
build-preprod:
runs-on: ubuntu-latest
needs: [deploy-dev]
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push PREPROD
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=preprod
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-prd
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} preprod! :this-is-fine:"
deploy-preprod:
needs: [deploy-dev, build-preprod]
runs-on: [ arc-azure-preprod ]
environment: preprod
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to preprod... :partydeploy:"
- name: Deploy to preprod
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBECONFIG_AZURE_PREPROD }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG-prd \
--wait -i -f k8s/values-preprod.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} on preprod! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to preprod! :gopher_party:"
build-cec-prd:
runs-on: ubuntu-latest
needs: [deploy-dev, deploy-preprod]
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push CEC-PRD
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=cec-prd
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-cec-prd
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} cec-prd! :this-is-fine:"
deploy-cec-prd:
needs: [deploy-dev, deploy-preprod, build-cec-prd]
runs-on: [ arc-azure-cec-prd ]
environment: prd
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to cec-prd... :partydeploy:"
- name: Deploy to cec-prd
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBECONFIG_AZURE_CEC_PRD }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG-cec-prd \
--wait -i -f k8s/values-cec-prd.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} on cec-prd! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to cec-prd! :gopher_party:"
build-cec-euprd:
runs-on: ubuntu-latest
needs: [deploy-dev, deploy-preprod]
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
- name: Checkout
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_DEV }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_DEV }}
- name: ACR Login
run: |
az acr login --name ${{ env.REGISTRY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push CEC-EUPRD
uses: docker/build-push-action@v5
with:
context: .
build-args: ENVIRONMENT=cec-euprd
push: true
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-cec-euprd
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify if failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to build ${{ env.PROJECT }} cec-euprd! :this-is-fine:"
deploy-cec-euprd:
needs: [deploy-dev, deploy-preprod, build-cec-euprd]
runs-on: [ arc-azure-cec-euprd ]
environment: prd
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify deploy
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to cec-euprd... :partydeploy:"
- name: Deploy to cec-euprd
id: deploy
uses: koslib/helm-eks-action@v1.28.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBECONFIG_AZURE_CEC_EUPRD }}
with:
command: |
helm upgrade \
--atomic \
--create-namespace \
--namespace default \
--set image.registry=$REGISTRY \
--set image.name=$PROJECT \
--set image.tag=$TAG-cec-euprd \
--wait -i -f k8s/values-cec-euprd.yaml $PROJECT k8s/
- name: Deploy Response
run: echo "Response was ${{ steps.deploy.outputs.response }}"
- name: Notify deploy failure
if: ${{ failure() }}
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to deploy ${{ env.PROJECT }} on cec-euprd! :this-is-fine:"
- name: Notify deploy success
uses: rtCamp/action-slack-notify@v2
env:
MSG_MINIMAL: true
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to cec-euprd! :gopher_party:"

View File

@@ -1,12 +0,0 @@
name: Pull Request Jira Check
on:
pull_request:
branches:
- main
types: [opened, edited, synchronize, reopened]
jobs:
prcheck:
uses: Fisker-Inc/github-actions/.github/workflows/pr.yml@v1.0.16

View File

@@ -1,35 +0,0 @@
name: Node.js CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm install
- run: npm run build --if-present
- run: npm test -- --coverage --coverageDirectory='coverage' --watchAll=false
env:
CI: true
- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

30
.vscode/launch.json vendored
View File

@@ -1,30 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${file}"
},
{
"name": "Debug CRA Tests",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
"args": ["test", "--runInBand", "--no-cache", "--watchAll=false"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"env": { "CI": "true" },
"disableOptimisticBPs": true
}
]
}

View File

@@ -1,7 +0,0 @@
{
"editor.formatOnSave": true,
// spacing
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false
}

View File

@@ -1,69 +1,42 @@
# Fisker Admin Portal # OTA Admin Portal
Front-end web application for administrating services. Front-end web application for OTA administration.
# Setup ## Setup
Running locally ```bash
npm install
npm start
# Access at http://localhost:3000
```
1. Install Node 16 ## Build & Deploy
2. Run `npm install`
3. Copy .env.template to .env and edit the service urls for authentication and api services
4. Run `./run.sh` from the terminal
5. Access portal at localhost:3000
Running Docker container ```bash
# Build and push Docker image
docker build --build-arg ENVIRONMENT=mini -t gitea.mini.cloud.fiskerinc.com/admin/ota-admin-portal:latest .
docker login gitea.mini.cloud.fiskerinc.com -u admin
docker push gitea.mini.cloud.fiskerinc.com/admin/ota-admin-portal:latest
```
1. Copy .env.template to .env and edit the service urls for authentication and api services ## Environment
2. Build the image `docker build --build-arg ENVIRONMENT=local -t fiskerinc/portal .`
3. Start the container `docker run -p 3000:80 fiskerinc/portal`
4. Access portal at localhost:3000
## Available Scripts - `.env.local` - Local development
- `.env.mini` - Mini cluster (Mac Mini)
In the project directory, you can run: ## Deployment
### `npm start` Deployed via ArgoCD from `k8s/` helm chart with `values-mini.yaml`.
Runs the app in the development mode.\ URL: https://ota-admin.mini.cloud.fiskerinc.com
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\ ## Mini Cluster Login (Keycloak)
You will also see any lint errors in the console.
### `npm test` The mini cluster uses Keycloak instead of Cognito. Test users for the `compute-auth` realm:
Launches the test runner in the interactive watch mode.\ | User | Password | Role |
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. |------|----------|------|
| admin | Admin123! | Full access |
| operator | Operator123! | Operator access |
### `npm run build` Keycloak admin console: https://keycloak.mini.cloud.fiskerinc.com (admin / admin123)
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

12
devbox.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/main/.schema/devbox.schema.json",
"packages": [
"nodejs_18",
"docker"
],
"shell": {
"init_hook": [
"echo 'OTA Admin Portal dev environment ready'"
]
}
}

View File

@@ -23,6 +23,7 @@ spec:
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag}}" image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag}}"
imagePullPolicy: {{ .Values.image.pullPolicy | default "Always" }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
env: env:

View File

@@ -1,12 +0,0 @@
ingress:
hostname: ota-admin.cec-euprd.fiskerinc.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
#cpu: 250m
memory: 256Mi
replicas: 3

View File

@@ -1,12 +0,0 @@
ingress:
hostname: ota-admin.cec-prd.fiskerinc.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
#cpu: 250m
memory: 256Mi
replicas: 3

View File

@@ -1,12 +0,0 @@
ingress:
hostname: dev-ota-admin.cloud.fiskerinc.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
#cpu: 250m
memory: 256Mi
replicas: 1

View File

@@ -3,14 +3,15 @@ ingress:
className: traefik className: traefik
image: image:
registry: gitea.mini.cloud.fiskerinc.com registry: localhost:32000
name: admin/ota-admin-portal name: ota-admin-portal
tag: latest tag: preprod4
pullPolicy: IfNotPresent
resources: resources:
requests: requests:
cpu: 100m cpu: 100m
memory: 128Mi memory: 256Mi
limits: limits:
memory: 256Mi memory: 256Mi

View File

@@ -1,12 +0,0 @@
ingress:
hostname: ota-admin.cloud.fiskerinc.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
#cpu: 250m
memory: 256Mi
replicas: 3

106
package-lock.json generated
View File

@@ -34,10 +34,12 @@
"leaflet": "^1.8.0", "leaflet": "^1.8.0",
"material-ui-dropzone": "^3.5.0", "material-ui-dropzone": "^3.5.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"oidc-client-ts": "^3.4.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-leaflet": "^3.2.5", "react-leaflet": "^3.2.5",
"react-leaflet-cluster": "^1.0.3", "react-leaflet-cluster": "^1.0.3",
"react-oidc-context": "^3.3.0",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
@@ -3952,6 +3954,21 @@
"string.prototype.matchall": "^4.0.6" "string.prototype.matchall": "^4.0.6"
} }
}, },
"node_modules/@surma/rollup-plugin-off-main-thread/node_modules/ejs": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@svgr/babel-plugin-add-jsx-attribute": { "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
"version": "5.4.0", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz",
@@ -7361,20 +7378,6 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.567", "version": "1.4.567",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz",
@@ -12215,6 +12218,27 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
}, },
"node_modules/oidc-client-ts": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz",
"integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==",
"license": "Apache-2.0",
"dependencies": {
"jwt-decode": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/oidc-client-ts/node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -14170,6 +14194,19 @@
"react-leaflet": "^3.0.2" "react-leaflet": "^3.0.2"
} }
}, },
"node_modules/react-oidc-context": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz",
"integrity": "sha512-302T/ma4AOVAxrHdYctDSKXjCq9KNHT564XEO2yOPxRfxEP58xa4nz+GQinNl8x7CnEXECSM5JEjQJk3Cr5BvA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"oidc-client-ts": "^3.1.0",
"react": ">=16.14.0"
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -19970,10 +20007,20 @@
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
"integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==",
"requires": { "requires": {
"ejs": "^3.1.6", "ejs": "3.1.9",
"json5": "^2.2.0", "json5": "^2.2.0",
"magic-string": "^0.25.0", "magic-string": "^0.25.0",
"string.prototype.matchall": "^4.0.6" "string.prototype.matchall": "^4.0.6"
},
"dependencies": {
"ejs": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
"requires": {
"jake": "^10.8.5"
}
}
} }
}, },
"@svgr/babel-plugin-add-jsx-attribute": { "@svgr/babel-plugin-add-jsx-attribute": {
@@ -22470,14 +22517,6 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"requires": {
"jake": "^10.8.5"
}
},
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.567", "version": "1.4.567",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz",
@@ -26023,6 +26062,21 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
}, },
"oidc-client-ts": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz",
"integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==",
"requires": {
"jwt-decode": "^4.0.0"
},
"dependencies": {
"jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="
}
}
},
"on-finished": { "on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -27253,6 +27307,12 @@
"leaflet.markercluster": "^1.4.1" "leaflet.markercluster": "^1.4.1"
} }
}, },
"react-oidc-context": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz",
"integrity": "sha512-302T/ma4AOVAxrHdYctDSKXjCq9KNHT564XEO2yOPxRfxEP58xa4nz+GQinNl8x7CnEXECSM5JEjQJk3Cr5BvA==",
"requires": {}
},
"react-refresh": { "react-refresh": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",

View File

@@ -29,10 +29,12 @@
"leaflet": "^1.8.0", "leaflet": "^1.8.0",
"material-ui-dropzone": "^3.5.0", "material-ui-dropzone": "^3.5.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"oidc-client-ts": "^3.4.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-leaflet": "^3.2.5", "react-leaflet": "^3.2.5",
"react-leaflet-cluster": "^1.0.3", "react-leaflet-cluster": "^1.0.3",
"react-oidc-context": "^3.3.0",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
@@ -48,18 +50,11 @@
}, },
"scripts": { "scripts": {
"start": "env-cmd -f .env.local react-scripts start", "start": "env-cmd -f .env.local react-scripts start",
"build": "env-cmd -f .env.local react-scripts build", "build": "env-cmd -f .env.mini react-scripts build",
"build:dev": "env-cmd -f .env.dev react-scripts build",
"build:stg": "env-cmd -f .env.stg react-scripts build",
"build:preprod": "env-cmd -f .env.prd react-scripts build",
"build:cec-prd": "env-cmd -f .env.cec-prd react-scripts build",
"build:cec-euprd": "env-cmd -f .env.cec-euprd react-scripts build",
"build:mini": "env-cmd -f .env.mini react-scripts build", "build:mini": "env-cmd -f .env.mini react-scripts build",
"build:local": "env-cmd -f .env.local react-scripts build", "build:local": "env-cmd -f .env.local react-scripts build",
"test": "env-cmd -f .env.local react-scripts test --no-cache", "test": "env-cmd -f .env.local react-scripts test --no-cache",
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache", "test:coverage": "npm test -- --coverage --watchAll=false --no-cache"
"test:coverage": "npm test -- --coverage --watchAll=false --no-cache",
"eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

6
run.sh
View File

@@ -1,6 +0,0 @@
#!/bin/bash
node -v
npm install
#cp .env.template .env
npm start

View File

@@ -1,6 +0,0 @@
sonar.projectKey=Fisker-Inc_ota-admin-portal
sonar.organization=fisker-inc
sonar.sourceEncoding=UTF-8
sonar.javascript.coveragePlugin=lcov
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.sources=src

View File

@@ -1,13 +1,15 @@
import React from "react"; import React from "react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "react-oidc-context";
import { UserProvider } from "../Contexts/UserContext"; import { UserProvider } from "../Contexts/UserContext";
import { StatusProvider } from "../Contexts/StatusContext"; import { StatusProvider } from "../Contexts/StatusContext";
import { CssBaseline } from "@material-ui/core"; import { CssBaseline } from "@material-ui/core";
import MenuDrawer from "../Layouts/MenuDrawer"; import MenuDrawer from "../Layouts/MenuDrawer";
import SiteRoutes from "../Routes/SiteRoutes"; import SiteRoutes from "../Routes/SiteRoutes";
import { } from "../../services/monitoring"; import { } from "../../services/monitoring";
import { keycloakConfig, isKeycloakEnabled } from "../../services/keycloak";
function App() { function AppContent() {
return ( return (
<StatusProvider> <StatusProvider>
<UserProvider> <UserProvider>
@@ -22,4 +24,25 @@ function App() {
); );
} }
const onSigninCallback = () => {
// Remove OIDC params from URL after signin
window.history.replaceState({}, document.title, window.location.pathname);
};
function App() {
// Only wrap with AuthProvider if Keycloak is enabled
if (isKeycloakEnabled()) {
return (
<AuthProvider
{...keycloakConfig}
onSigninCallback={onSigninCallback}
>
<AppContent />
</AuthProvider>
);
}
return <AppContent />;
}
export default App; export default App;

View File

@@ -3,6 +3,7 @@ import auth from "../../services/auth";
import getTimerWorker from "../../services/getTimerWorker"; import getTimerWorker from "../../services/getTimerWorker";
import { parsePayload } from "../../utils/jwt"; import { parsePayload } from "../../utils/jwt";
import {getGroups, getProviders} from "../../utils/roles"; import {getGroups, getProviders} from "../../utils/roles";
import { isKeycloakEnabled } from "../../services/keycloak";
const UserContext = React.createContext(); const UserContext = React.createContext();
@@ -12,6 +13,7 @@ export const UserProvider = ({ children }) => {
const [groups, setGroups] = useState(null); const [groups, setGroups] = useState(null);
const [providers, setProviders] = useState(null); const [providers, setProviders] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [authSource, setAuthSource] = useState(null); // 'cognito' or 'keycloak'
let timer; let timer;
useEffect(() => { useEffect(() => {
@@ -19,8 +21,18 @@ export const UserProvider = ({ children }) => {
if (!localStorage) return; if (!localStorage) return;
const t = JSON.parse(localStorage.getItem("token")); const t = JSON.parse(localStorage.getItem("token"));
if (!t) return; if (!t) return;
// Check if it's a Keycloak token (different structure)
if (t.authSource === 'keycloak') {
setToken(t);
setAuthSource('keycloak');
return;
}
// Cognito token format
if (!t.idToken || !t.idToken.jwtToken) throw new Error("Invalid token"); if (!t.idToken || !t.idToken.jwtToken) throw new Error("Invalid token");
setToken(t); setToken(t);
setAuthSource('cognito');
} catch (e) { } catch (e) {
document.location = signOut(); document.location = signOut();
} }
@@ -29,12 +41,16 @@ export const UserProvider = ({ children }) => {
useEffect(() => { useEffect(() => {
if (!token) return; if (!token) return;
verifyToken(); if (authSource === 'keycloak') {
verifyKeycloakToken();
} else {
verifyToken();
}
return () => { return () => {
if (timer) timer.terminate(); if (timer) timer.terminate();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]); }, [token, authSource]);
const refreshTokens = async () => { const refreshTokens = async () => {
if (!token || !token.refreshToken || !token.refreshToken.token) return null; if (!token || !token.refreshToken || !token.refreshToken.token) return null;
@@ -42,25 +58,57 @@ export const UserProvider = ({ children }) => {
}; };
const startSessionTimer = () => { const startSessionTimer = () => {
if (!token || !token.idToken || !token.idToken.jwtToken) { let jwtToken;
throw new Error("No id token"); if (authSource === 'keycloak') {
jwtToken = token.idToken;
} else {
if (!token || !token.idToken || !token.idToken.jwtToken) {
throw new Error("No id token");
}
jwtToken = token.idToken.jwtToken;
} }
const payload = parsePayload(token.idToken.jwtToken);
const payload = parsePayload(jwtToken);
if (!payload || !payload.exp) throw new Error("Bad id token payload"); if (!payload || !payload.exp) throw new Error("Bad id token payload");
const duration = 1000 * payload.exp - new Date().getTime(); const duration = 1000 * payload.exp - new Date().getTime();
if (!timer) { if (!timer) {
timer = getTimerWorker(); timer = getTimerWorker();
timer.onMessage(async (e) => { timer.onMessage(async (e) => {
if (e.data === "timeout") { if (e.data === "timeout") {
const t = await refreshTokens(); if (authSource === 'keycloak') {
if (t && !t.error) return; // For Keycloak, just sign out - OIDC library handles refresh
document.location = signOut(); document.location = signOut();
} else {
const t = await refreshTokens();
if (t && !t.error) return;
document.location = signOut();
}
} }
}); });
} }
timer.start(duration); timer.start(duration);
}; };
const verifyKeycloakToken = async () => {
try {
const idToken = token.idToken;
const payload = parsePayload(idToken);
// For Keycloak, we trust the token if it's not expired
if (!payload || !payload.exp) throw new Error("Invalid Keycloak token");
if (payload.exp * 1000 < Date.now()) throw new Error("Token expired");
// Extract groups from Keycloak token (realm_access.roles or groups claim)
const keycloakGroups = payload.groups || payload.realm_access?.roles || [];
setGroups(keycloakGroups);
setProviders([]);
startSessionTimer();
} catch (e) {
setError(`Keycloak verify error. ${e.message}`);
document.location = signOut();
}
};
const verifyToken = async () => { const verifyToken = async () => {
try { try {
const { const {
@@ -96,6 +144,7 @@ export const UserProvider = ({ children }) => {
throw new Error(result.message); throw new Error(result.message);
} }
setAuthSource('cognito');
signedIn(result); signedIn(result);
} catch (err) { } catch (err) {
setError(`Sign in error. ${err.message}`); setError(`Sign in error. ${err.message}`);
@@ -106,13 +155,42 @@ export const UserProvider = ({ children }) => {
return result; return result;
}; };
// Sign in with Keycloak OIDC token
const signInWithKeycloak = (oidcUser) => {
if (!oidcUser || !oidcUser.id_token) return;
const keycloakToken = {
authSource: 'keycloak',
idToken: oidcUser.id_token,
accessToken: oidcUser.access_token,
refreshToken: oidcUser.refresh_token,
profile: oidcUser.profile,
};
setAuthSource('keycloak');
setToken(keycloakToken);
if (localStorage) {
localStorage.setItem("token", JSON.stringify(keycloakToken));
}
};
const signOut = () => { const signOut = () => {
setGroups(null); setGroups(null);
setProviders(null); setProviders(null);
setToken(null); setToken(null);
setAuthSource(null);
if (localStorage) { if (localStorage) {
localStorage.removeItem("token"); localStorage.removeItem("token");
} }
// For Keycloak, we need to redirect to Keycloak logout
if (authSource === 'keycloak' && isKeycloakEnabled()) {
const keycloakUrl = process.env.REACT_APP_KEYCLOAK_URL || 'https://keycloak.mini.cloud.fiskerinc.com';
const realm = process.env.REACT_APP_KEYCLOAK_REALM || 'compute-auth';
const redirectUri = process.env.REACT_APP_AUTH_CALLBACK_URL;
return `${keycloakUrl}/realms/${realm}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(redirectUri)}`;
}
return getLogoutURL(); return getLogoutURL();
}; };
@@ -158,10 +236,12 @@ export const UserProvider = ({ children }) => {
groups, groups,
providers, providers,
token, token,
authSource,
getAuthorizeURL, getAuthorizeURL,
getLogoutURL, getLogoutURL,
setError, setError,
signIn, signIn,
signInWithKeycloak,
signOut, signOut,
refresh, refresh,
}} }}

View File

@@ -1,9 +1,11 @@
import { Button } from "@material-ui/core"; import { Button } from "@material-ui/core";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useAuth } from "react-oidc-context";
import { useUserContext } from "../Contexts/UserContext"; import { useUserContext } from "../Contexts/UserContext";
import useStyles from "../useStyles"; import useStyles from "../useStyles";
import { isKeycloakEnabled } from "../../services/keycloak";
const getCode = (search) => { const getCode = (search) => {
if (!search) return null; if (!search) return null;
@@ -11,7 +13,66 @@ const getCode = (search) => {
return s.get("code"); return s.get("code");
}; };
export default function SignInForm() { // Keycloak-enabled version of the form - only rendered when inside AuthProvider
function KeycloakSignInForm() {
const classes = useStyles();
const { getAuthorizeURL, signIn, signInWithKeycloak, fetching } = useUserContext();
const auth = useAuth();
// Handle Cognito callback
useEffect(() => {
const code = getCode(document.location.search);
// Only process if it's a Cognito code (not a Keycloak callback)
if (!code || auth?.isLoading || auth?.isAuthenticated) return;
signIn(code);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Handle Keycloak authentication callback
useEffect(() => {
if (auth?.isAuthenticated && auth?.user) {
signInWithKeycloak(auth.user);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [auth?.isAuthenticated, auth?.user]);
const handleKeycloakLogin = () => {
auth.signinRedirect();
};
return (
<div className={clsx(classes.paper, classes.textJustifyAlign)}>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submit}
href={getAuthorizeURL()}
disabled={fetching}
>
{fetching ? "Please wait..." : "Sign In with Cognito"}
</Button>
<div style={{ margin: '16px 0', textAlign: 'center', color: '#666' }}>
or
</div>
<Button
variant="outlined"
color="primary"
className={classes.submit}
onClick={handleKeycloakLogin}
disabled={fetching || auth?.isLoading}
>
{auth?.isLoading ? "Loading..." : "Sign In with Keycloak"}
</Button>
<p><strong>Note: Your email address will be used as the user id</strong></p>
</div>
);
}
// Standard Cognito-only form
function CognitoSignInForm() {
const classes = useStyles(); const classes = useStyles();
const { getAuthorizeURL, signIn, fetching } = useUserContext(); const { getAuthorizeURL, signIn, fetching } = useUserContext();
@@ -38,3 +99,10 @@ export default function SignInForm() {
</div> </div>
); );
} }
export default function SignInForm() {
if (isKeycloakEnabled()) {
return <KeycloakSignInForm />;
}
return <CognitoSignInForm />;
}

21
src/services/keycloak.js Normal file
View File

@@ -0,0 +1,21 @@
// Keycloak OIDC configuration
const KEYCLOAK_URL = process.env.REACT_APP_KEYCLOAK_URL || 'https://keycloak.mini.cloud.fiskerinc.com';
const KEYCLOAK_REALM = process.env.REACT_APP_KEYCLOAK_REALM || 'compute-auth';
const KEYCLOAK_CLIENT_ID = process.env.REACT_APP_KEYCLOAK_CLIENT_ID || 'ota-portal';
const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL;
export const keycloakConfig = {
authority: `${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}`,
client_id: KEYCLOAK_CLIENT_ID,
redirect_uri: CALLBACK_URL,
post_logout_redirect_uri: CALLBACK_URL,
response_type: 'code',
scope: 'openid',
// Handle silent renew and callback automatically
automaticSilentRenew: true,
loadUserInfo: true,
};
export const isKeycloakEnabled = () => {
return process.env.REACT_APP_KEYCLOAK_ENABLED === 'true';
};