diff --git a/backend/src/predicTCR_server/app.py b/backend/src/predicTCR_server/app.py index 8a79e4b..fb1af0d 100644 --- a/backend/src/predicTCR_server/app.py +++ b/backend/src/predicTCR_server/app.py @@ -446,6 +446,7 @@ def runner_result(): sources="TIL;PMBC;Other", csv_required_columns="barcode;cdr3;chain", runner_job_timeout_mins=60, + about_md="", ) ) db.session.commit() diff --git a/backend/src/predicTCR_server/model.py b/backend/src/predicTCR_server/model.py index 0b9bc4c..45fdab9 100644 --- a/backend/src/predicTCR_server/model.py +++ b/backend/src/predicTCR_server/model.py @@ -53,6 +53,7 @@ class Settings(db.Model): sources: Mapped[str] = mapped_column(String, nullable=False) csv_required_columns: Mapped[str] = mapped_column(String, nullable=False) runner_job_timeout_mins: Mapped[int] = mapped_column(Integer, nullable=False) + about_md: Mapped[str] = mapped_column(String, nullable=False) def as_dict(self): return {c: getattr(self, c) for c in inspect(self).attrs.keys()} diff --git a/backend/tests/test_app.py b/backend/tests/test_app.py index 7279674..3a59e39 100644 --- a/backend/tests/test_app.py +++ b/backend/tests/test_app.py @@ -142,6 +142,7 @@ def test_get_settings_valid(client): "sources": "TIL;PMBC;Other", "tumor_types": "Lung;Breast;Other", "runner_job_timeout_mins": 60, + "about_md": "", } @@ -156,6 +157,7 @@ def test_update_settings_valid(client): "sources": "a;b;g", "tumor_types": "1;2;6", "runner_job_timeout_mins": 12, + "about_md": "# About", "invalid-key": "invalid", } response = client.post("/api/admin/settings", headers=headers, json=new_settings) diff --git a/frontend/package.json b/frontend/package.json index fdd65bb..774e490 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,9 @@ "bootstrap-icons": "^1.11.3", "flowbite": "^2.5.2", "flowbite-vue": "^0.1.6", + "github-markdown-css": "^5.8.1", "pinia": "^2.2.2", + "showdown": "^2.1.0", "vue": "^3.5.10", "vue-router": "^4.4.5", "vueperslides": "^3.5.1" @@ -27,6 +29,7 @@ "@testing-library/vue": "^8.1.0", "@types/jsdom": "^21.1.7", "@types/node": "^22.7.4", + "@types/showdown": "^2.0.6", "@typescript-eslint/eslint-plugin": "^8.7.0", "@typescript-eslint/parser": "^8.7.0", "@vitejs/plugin-vue": "^5.1.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 941fe14..ffe90ff 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -19,9 +19,15 @@ importers: flowbite-vue: specifier: ^0.1.6 version: 0.1.6(tailwindcss@3.4.13)(vue@3.5.10(typescript@5.5.4)) + github-markdown-css: + specifier: ^5.8.1 + version: 5.8.1 pinia: specifier: ^2.2.2 version: 2.2.2(typescript@5.5.4)(vue@3.5.10(typescript@5.5.4)) + showdown: + specifier: ^2.1.0 + version: 2.1.0 vue: specifier: ^3.5.10 version: 3.5.10(typescript@5.5.4) @@ -44,6 +50,9 @@ importers: "@types/node": specifier: ^22.7.4 version: 22.7.4 + "@types/showdown": + specifier: ^2.0.6 + version: 2.0.6 "@typescript-eslint/eslint-plugin": specifier: ^8.7.0 version: 8.7.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4) @@ -966,6 +975,12 @@ packages: integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==, } + "@types/showdown@2.0.6": + resolution: + { + integrity: sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==, + } + "@types/tough-cookie@4.0.5": resolution: { @@ -1782,6 +1797,13 @@ packages: } engines: { node: ">= 6" } + commander@9.5.0: + resolution: + { + integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==, + } + engines: { node: ^12.20.0 || >=14 } + computeds@0.0.1: resolution: { @@ -2476,6 +2498,13 @@ packages: } engines: { node: ">= 0.4" } + github-markdown-css@5.8.1: + resolution: + { + integrity: sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==, + } + engines: { node: ">=10" } + glob-parent@5.1.2: resolution: { @@ -4001,6 +4030,13 @@ packages: integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==, } + showdown@2.1.0: + resolution: + { + integrity: sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==, + } + hasBin: true + side-channel@1.0.6: resolution: { @@ -5313,6 +5349,8 @@ snapshots: "@types/resolve@1.20.2": {} + "@types/showdown@2.0.6": {} + "@types/tough-cookie@4.0.5": {} "@types/web-bluetooth@0.0.20": {} @@ -5928,6 +5966,8 @@ snapshots: commander@4.1.1: {} + commander@9.5.0: {} + computeds@0.0.1: {} concat-map@0.0.1: {} @@ -6466,6 +6506,8 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 + github-markdown-css@5.8.1: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7289,6 +7331,10 @@ snapshots: shell-quote@1.8.1: {} + showdown@2.1.0: + dependencies: + commander: 9.5.0 + side-channel@1.0.6: dependencies: call-bind: 1.0.7 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index a4e0897..3be933d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -8,8 +8,11 @@ import { FwbNavbarLogo, } from "flowbite-vue"; import { useUserStore } from "@/stores/user"; +import { useSettingsStore } from "@/stores/settings"; import FooterComponent from "@/components/FooterComponent.vue"; const userStore = useUserStore(); +const settingsStore = useSettingsStore(); +settingsStore.refresh(); const login_title = computed(() => { if (userStore.user !== null) { return userStore.user.email; @@ -21,14 +24,17 @@ const login_title = computed(() => {