diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..61b1320 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_TARGO_BACKEND_URL=PREFIX_BACKEND_URL \ No newline at end of file diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md new file mode 100644 index 0000000..adc5379 --- /dev/null +++ b/.gitea/workflows/README.md @@ -0,0 +1 @@ +Workflows to be compliant with CI/CD pipelines \ No newline at end of file diff --git a/.gitea/workflows/node-ci.yaml b/.gitea/workflows/node-ci.yaml new file mode 100644 index 0000000..76a34ea --- /dev/null +++ b/.gitea/workflows/node-ci.yaml @@ -0,0 +1,150 @@ +name: Node-CI + + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-24.04 + steps: + - name: Gitea Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + - name: Notify Google Chat + if: ${{ failure() }} # Use always to ensure that the notification is also send on failure of former steps + uses: SimonScholz/google-chat-action@3b3519e5102dba8aa5046fd711c4b553586409bb # v1.1.0 + with: + webhookUrl: '${{ secrets.GOOGLE_CHAT_WEBHOOK }}' + jobStatus: ${{ job.status }} + title: Lint failed + + test: + runs-on: ubuntu-24.04 + steps: + - name: Gitea Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Notify Google Chat + if: ${{ failure() }} # Use always to ensure that the notification is also send on failure of former steps + uses: SimonScholz/google-chat-action@3b3519e5102dba8aa5046fd711c4b553586409bb # v1.1.0 + with: + webhookUrl: '${{ secrets.GOOGLE_CHAT_WEBHOOK }}' + jobStatus: ${{ job.status }} + title: Test failed + + build: + runs-on: ubuntu-24.04 + steps: + - name: Gitea Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run build + run: npm run build + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: dist + retention-days: 1 + + - uses: actions/upload-artifact@v3 + with: + name: source + path: src + retention-days: 1 + - uses: actions/upload-artifact@v3 + with: + name: Dockerfile + path: Dockerfile + retention-days: 1 + + - name: Notify Google Chat + if: ${{ failure() }} # Use always to ensure that the notification is also send on failure of former steps + uses: SimonScholz/google-chat-action@3b3519e5102dba8aa5046fd711c4b553586409bb # v1.1.0 + with: + webhookUrl: '${{ secrets.GOOGLE_CHAT_WEBHOOK }}' + jobStatus: ${{ job.status }} + title: Build failed + + + deploy: + needs: [lint, test, build] + runs-on: ubuntu-24.04 + #if: github.ref == 'refs/heads/main' + permissions: + contents: read + id-token: write + steps: + - name: Getting Dockerfile + uses: actions/download-artifact@v3 + with: + name: Dockerfile + - name: Downloading dist folder + uses: actions/download-artifact@v3 + with: + name: dist + path: dist + - name: Downloading src folder + uses: actions/download-artifact@v3 + with: + name: source + path: source + - name: Set version number + run: echo "VERSION_NUMBER=$(date +'%y%m%d.%H%M%S')" >> $GITHUB_ENV + - name: Building docker image + run: docker build -t git.targo.ca/targo/targo-frontend-staging:2.${{ env.VERSION_NUMBER }} . + - name: Add tag Latest + run: docker tag git.targo.ca/targo/targo-frontend-staging:2.${{ env.VERSION_NUMBER }} git.targo.ca/targo/targo-frontend-staging:latest + - name: Push images to server + run: | + docker login -u ${{ secrets.CI_USER }} -p ${{ secrets.CI_PASSWORD }} git.targo.ca + docker push git.targo.ca/targo/targo-frontend-staging:2.${{ env.VERSION_NUMBER }} + docker push git.targo.ca/targo/targo-frontend-staging:latest + curl --location 'https://n8napi.targo.ca/webhook/portainer' --header 'Authorization: Basic ${{ secrets.API_SECRET}}' --form 'stack="new_targo_app_staging"' diff --git a/Dockerfile b/Dockerfile index 9ec98a1..73f79ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,15 @@ -# Step 1 - Building the app -FROM node:22 AS build -WORKDIR /app -COPY package*.json ./ -# Set environment variables -ARG BACKEND_URL -ENV VITE_TARGO_BACKEND_URL=$BACKEND_URL -# Install dependencies -RUN npm install -g @quasar/cli -COPY . . -RUN npm install -RUN npm run build - -# Step 2 - Move Applicatin to Nginx FROM nginx:alpine -COPY --from=build /app/dist/spa /usr/share/nginx/html + +#Copy files RUN mkdir /usr/share/nginx/html/src -COPY --from=build /app/src /usr/share/nginx/html/src +COPY dist/spa /usr/share/nginx/html +COPY source /usr/share/nginx/html/src + +#Add script to replace VITE env vars +RUN curl -o /docker-entrypoint.d/env.sh -O https://raw.githubusercontent.com/Dutchskull/Vite-Dynamic-Environment-Variables/refs/heads/main/app/env.sh +RUN dos2unix /docker-entrypoint.d/env.sh +RUN chmod +x /docker-entrypoint.d/env.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"] diff --git a/env.sh b/env.sh new file mode 100644 index 0000000..e7cd7be --- /dev/null +++ b/env.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env sh +# ================================================================================ +# File: env.sh +# Description: Replaces environment variables in asset files. +# Usage: Run this script in your terminal, ensuring APP_PREFIX and ASSET_DIRS are set. +# ================================================================================ + +# Set the exit flag to exit immediately if any command fails +set -e + +# Check if APP_PREFIX is set +: "${APP_PREFIX:?APP_PREFIX must be set (e.g. APP_PREFIX='APP_PREFIX_')}" + +# Check if ASSET_DIRS is set +: "${ASSET_DIR:?Must set ASSET_DIR to one path}" + +# Check if the directory exists +if [ ! -d "$ASSET_DIR" ]; then + # If not, display a warning message and skip to the next iteration + echo "Warning: directory '$ASSET_DIR' not found, skipping." + continue +fi + +# Display the current directory being scanned +echo "Scanning directory: $ASSET_DIR" + +# Iterate through each environment variable that starts with APP_PREFIX +env | grep "^${APP_PREFIX}" | while IFS='=' read -r key value; do + # Display the variable being replaced + echo " • Replacing ${key} → ${value}" + + # Use find and sed to replace the variable in all files within the directory + find "$ASSET_DIR" -type f \ + -exec sed -i "s|${key}|${value}|g" {} + +done diff --git a/quasar.config.ts b/quasar.config.ts index 8e9ee05..205b2f3 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -41,7 +41,7 @@ export default defineConfig((ctx) => { build: { target: { browser: [ 'es2022', 'firefox115', 'chrome115', 'safari14' ], - node: 'node20' + node: 'node20', }, typescript: { @@ -66,7 +66,11 @@ export default defineConfig((ctx) => { // polyfillModulePreload: true, // distDir - // extendViteConf (viteConf) {}, + extendViteConf: (_config) => ({ + optimizeDeps: { + exclude: ['tesseract.js'] + } + }), // viteVuePluginOptions: {}, vitePlugins: [ diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 3ac8c4c..612d74e 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -16,7 +16,8 @@ declare module 'vue' { // for each client) const api = axios.create({ baseURL: import.meta.env.VITE_TARGO_BACKEND_URL, - withCredentials: true + withCredentials: true, + timeout: 5 * 60 * 1000, }); export default defineBoot(({ app }) => { diff --git a/src/i18n/en-ca/index.ts b/src/i18n/en-ca/index.ts index db4d786..9af138a 100644 --- a/src/i18n/en-ca/index.ts +++ b/src/i18n/en-ca/index.ts @@ -5,8 +5,11 @@ export default { chat_placeholder: "Enter a Message", chat_thinking: "Thinking...", error: { - NO_REPLY_RECEIVED: "encountered an error while waiting for chatbot to reply", - SEND_MESSAGE_FAILED: "unable to send message to chatbot", + NO_REPLY_RECEIVED: "FRONTEND ERROR: the backend did not include a message text in the response", + NO_DATA_RECEIVED: "BACKEND ERROR: the backend did not receive any data from the AI agent", + NO_OUTPUT_RECEIVED: "BACKEND ERROR: the backend received data, but no output, from the AI agent", + SEND_MESSAGE_FAILED: "FRONTEND ERROR: Your message could not send or was timed out", + GENERIC_RESPONSE_ERROR: "BACKEND ERROR: An unexpected error occured while waiting for the backend to respond", }, }, @@ -245,6 +248,8 @@ export default { day: "day", empty: "empty", name: "name", + lock: "", + unlock: "", }, misc: { or: "or", @@ -290,6 +295,8 @@ export default { apply_preset_day: "Apply schedule to day", apply_preset_week: "Apply schedule to week", save_successful: "timesheets saved", + unsaved_changes_title: "You have unsaved changes", + unsaved_changes_caption: "Save before leaving?", nav_button: { calendar_date_picker: "Calendar", current_week: "This week", @@ -331,6 +338,8 @@ export default { empty_list: 'No registered expenses', employee_comment: 'Comment', supervisor_comment: 'Supervisor note', + no_attachment: "no image attached", + temp_attachment_msg: "attachments are temporarily down, you can omit them from submissions and forward them to the finance department", actions: { delete_confirm: "Delete this expense?", }, @@ -347,9 +356,10 @@ export default { type: "Type", types: { PER_DIEM: "Per Diem", - EXPENSES: "expense", + EXPENSES: "reimbursement", MILEAGE: "mileage", ON_CALL: "on-call allowance", + COMMISSION: "Commission", }, }, errors: { diff --git a/src/i18n/fr-ca/index.ts b/src/i18n/fr-ca/index.ts index 935cdb4..a76fdd9 100644 --- a/src/i18n/fr-ca/index.ts +++ b/src/i18n/fr-ca/index.ts @@ -5,8 +5,11 @@ export default { chat_placeholder: "Entrez un Message", chat_thinking: "Réflexion en cours...", error: { - NO_REPLY_RECEIVED: "Une erreur est survenu lors de la réception de la réponse du chatbot", - SEND_MESSAGE_FAILED: "Une erreur est survenu lors de l'envoi de votre message", + NO_REPLY_RECEIVED: "ERREUR FRONTEND: Le frontend n'a pas recu de message de l'IA dans la réponse du backend", + NO_DATA_RECEIVED: "ERREUR BACKEND: L'agent IA n'a pas inclu de data dans sa réponse au backend", + NO_OUTPUT_RECEIVED: "ERREUR BACKEND: L'agent IA a inclu du data, mais pas de output, dans sa réponse au backend", + SEND_MESSAGE_FAILED: "ERREUR FRONTEND: Une erreur est survenue lors de l'envoi de votre message, ou la connexion a été perdu", + GENERIC_RESPONSE_ERROR: "ERREUR BACKEND: Une erreur est survenu lors de la réception de la réponse du AI", }, }, @@ -245,6 +248,8 @@ export default { day: "jour", empty: "vide", name: "nom", + lock: "verrouiller", + unlock: "déverrouiller", }, misc: { or: "ou", @@ -290,6 +295,8 @@ export default { apply_preset_day: "Appliquer horaire pour la journée", apply_preset_week: "Appliquer horaire pour la semaine", save_successful: "feuilles de temps enregistrées", + unsaved_changes_title: "Vous avez des changements non-enregistrés", + unsaved_changes_caption: "Sauvegardez avant de quitter?", nav_button: { calendar_date_picker: "Calendrier", current_week: "Semaine actuelle", @@ -331,6 +338,8 @@ export default { empty_list: 'Aucun dépense enregistrée', employee_comment: 'Commentaire', supervisor_comment: 'Note du Superviseur', + no_attachment: "aucune pièce jointe", + temp_attachment_msg: "Les pièces jointes sont désactivés temporairement, vous pouvez laisser le champ vide et acheminez vos recus au département de la comptabilité", actions: { delete_confirm: "Supprimer cette dépense?", }, @@ -347,9 +356,10 @@ export default { type: "Type", types: { PER_DIEM: "Per diem", - EXPENSES: "dépense", + EXPENSES: "remboursement", MILEAGE: "kilométrage", ON_CALL: "Prime de garde", + COMMISSION: "Commission", }, }, errors: { diff --git a/src/layouts/components/main-layout-header-bar.vue b/src/layouts/components/main-layout-header-bar.vue index 5ad6d39..71c6eee 100644 --- a/src/layouts/components/main-layout-header-bar.vue +++ b/src/layouts/components/main-layout-header-bar.vue @@ -2,9 +2,11 @@ lang="ts" setup > - import { useUiStore } from 'src/stores/ui-store'; + import { useAuthStore } from 'src/stores/auth-store'; +import { useUiStore } from 'src/stores/ui-store'; const uiStore = useUiStore(); + const authStore = useAuthStore(); diff --git a/src/layouts/components/main-layout-left-drawer.vue b/src/layouts/components/main-layout-left-drawer.vue index 498d4cf..5c567bb 100644 --- a/src/layouts/components/main-layout-left-drawer.vue +++ b/src/layouts/components/main-layout-left-drawer.vue @@ -9,6 +9,7 @@ import { useUiStore } from 'src/stores/ui-store'; import { RouteNames } from 'src/router/router-constants'; import { ModuleNames, type UserModuleAccess } from 'src/modules/shared/models/user.models'; +import { useAuthApi } from 'src/modules/auth/composables/use-auth-api'; const DRAWER_BUTTONS: { i18n_key: string, icon: string, route: RouteNames, required_module?: UserModuleAccess }[] = [ { i18n_key: 'nav_bar.home', icon: "home", route: RouteNames.DASHBOARD, required_module: ModuleNames.DASHBOARD }, @@ -21,6 +22,7 @@ const q = useQuasar(); const auth_store = useAuthStore(); + const authApi = useAuthApi(); const ui_store = useUiStore(); const router = useRouter(); const is_mini = ref(true); @@ -33,12 +35,8 @@ }); }; - const handleLogout = () => { - auth_store.logout(); - - router.push({ name: 'login' }).catch(err => { - console.error('could not log you out: ', err); - }) + const handleLogout = async () => { + await authApi.logout(); }; onMounted(() => { diff --git a/src/modules/auth/components/login-connection-panel.vue b/src/modules/auth/components/login-connection-panel.vue index 0dd7755..dfeb369 100644 --- a/src/modules/auth/components/login-connection-panel.vue +++ b/src/modules/auth/components/login-connection-panel.vue @@ -12,6 +12,15 @@ // const is_remembered = ref(false); const is_employee_email = computed(() => email.value.includes('@targ')); const is_game_time = computed(() => email.value.includes('allumette')); + + const onSubmitConnectionRequest = () => { + console.log('submit requested'); + if (is_employee_email.value) return; + } + + const onClickEmployeeConnect = () => { + auth_api.oidcLogin(); + }