From ecc332b38efb94577b0fd2870601d60029238c51 Mon Sep 17 00:00:00 2001 From: Luke Schreiber <131496683+Luke-Schreiber@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:05:54 -0600 Subject: [PATCH] Implement user login system --- .../docker-compose-local.template.yml | 98 ++++++- .gitignore | 1 + README.md | 26 +- apps/client/src/App.vue | 22 ++ .../client/src/components/auth/LoginModal.vue | 197 +++++++++++++ apps/client/src/pages/AdminPage.vue | 276 ++++++++++++++++++ apps/client/src/router/index.ts | 4 + .../datasetSelectionUntrrackedStore.ts | 44 ++- apps/client/src/stores/misc/authStore.ts | 69 +++++ apps/client/src/stores/misc/configStore.ts | 7 +- .../docs/getting-started-with-loon/data.md | 14 +- apps/server/api/views.py | 105 ++++++- apps/server/debug_data.py | 30 ++ apps/server/passwords.json | 6 + apps/server/passwords.json.example | 3 + apps/server/server/settings.py | 6 +- apps/server/server/urls.py | 16 +- build.py | 20 +- 18 files changed, 879 insertions(+), 65 deletions(-) create mode 100644 apps/client/src/components/auth/LoginModal.vue create mode 100644 apps/client/src/pages/AdminPage.vue create mode 100644 apps/client/src/stores/misc/authStore.ts create mode 100644 apps/server/debug_data.py create mode 100644 apps/server/passwords.json create mode 100644 apps/server/passwords.json.example diff --git a/.build-files/docker-compose-local.template.yml b/.build-files/docker-compose-local.template.yml index bd699b5f..80ec9004 100644 --- a/.build-files/docker-compose-local.template.yml +++ b/.build-files/docker-compose-local.template.yml @@ -22,12 +22,107 @@ services: - path: ${DOCKER_ENV_FILE} # relative to docker compose (has to be in same directory) required: true + # Depends on database. Database must be healthy before starting server + server: + build: + context: ../ # relative to docker compose + dockerfile: ./.build-files/Dockerfile.server # relatvie to build context + args: + DOCKER_ENV_FILE: ${DOCKER_ENV_FILE} + ports: + - "8000:8000" + environment: + DJANGO_ENV_FILE: "/app/.env" + volumes: + # - ../apps/server/api/migrations:/app/api/migrations + - ${MIGRATIONS_SOURCE}:/app/api/migrations + - ${LOCAL_DATA_VOLUME_LOCATION}:/app/data + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + data: + condition: service_started + + celery: + build: + context: ../ # relative to docker compose + dockerfile: ./.build-files/Dockerfile.celery # relatvie to build context + args: + DOCKER_ENV_FILE: ${DOCKER_ENV_FILE} + command: + [ + "celery", + "--app", + "celery_app", + "worker", + "--loglevel", + "info", + "--without-heartbeat", + "-c", + "4", + ] + # Docker Compose does not set the TTY width, which causes Celery errors + tty: false + environment: + DJANGO_ENV_FILE: "/app/.env" + volumes: + - ${LOCAL_DATA_VOLUME_LOCATION}:/app/data + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + data: + condition: service_started + + # MySQL service. Has healthcheck which uses mysqladmin to ping before reporting healthy. + db: + image: mysql:latest + environment: + MYSQL_DATABASE: ${DATABASE_NAME} + MYSQL_USER: ${DATABASE_USER} + MYSQL_PASSWORD: ${DATABASE_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} + volumes: + - mysql-data:/var/lib/mysql + ports: + - "3306:3306" + healthcheck: + test: + [ + "CMD", + "mysqladmin", + "ping", + "-h", + "localhost", + "-u", + "root", + "-p${DATABASE_ROOT_PASSWORD}", + ] + timeout: 20s + retries: 10 + env_file: + - path: ${DOCKER_ENV_FILE} # relative to docker compose (has to be in same directory) + required: true + + redis: + image: redis:latest + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + data: build: context: ./ dockerfile: Dockerfile.data ports: - - "9000:9000" + - "9001:9000" volumes: - ${LOCAL_DATA_VOLUME_LOCATION}:/app/data # relative to docker compose duckdb: @@ -40,5 +135,6 @@ services: - duckdb-data:/app/duckdb-data volumes: + mysql-data: duckdb-data: migrations_volume: diff --git a/.gitignore b/.gitignore index ee8290f3..39aa557f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ certs #Random apps/server/api/*.jpg apps/server/api/secrets.json +apps/server/passwords.json .env .env.production .env.* diff --git a/README.md b/README.md index 8cd187cc..06404e42 100644 --- a/README.md +++ b/README.md @@ -54,29 +54,34 @@ In the root of the repository, run the following following command: python3 build.py --prepare-dev ``` -### Prepare Development Environment and Run Separately with Experiment Visibility Control +### User Authentication & Experiment Gating -Experiment visibility control allows users to control which experiments are visible to them. You specify which experiments are visible to users in the `aa_index.json` file. +Loon supports user-based access control. You can manage users and define which experiments are visible to specific users. -```bash -python3 build.py --prepare-dev --experiment-visibility-control -``` +#### Managing Users +Admin users can manage accounts and passwords via the **Admin Control Panel** accessible at `/admin` within the application. -Experiment visibility control example: +#### Gating Experiments +Access is defined in the `aa_index.json` file using the `users` array. Experiments can be either public (string) or gated (object with `users` array). +Example `aa_index.json`: ```json { "experiments": [ { - "filename": "Data Discovery - Automated Cell Lineage Tracking.json", - "visible-by-default": true + "filename": "restricted_experiment.json", + "users": ["lab", "jess"] }, - "Quality Control - Cancer Triggering Microenvironments.json", - "Communication - Tumorigenic Cell States.json" + "public_experiment.json" ] } ``` +Experiments are filtered as follows: +- **Plain string**: Visible to everyone. +- **Admin**: Sees all experiments regardless of gating. +- **Gated (users list)**: Only visible to listed users and administrators. + This will generate the necessary environment file in the client directory while still running the rest of the container. Then, the development server can be started separately by the following: @@ -133,7 +138,6 @@ The build script will do its best to validate the config before starting the doc | -o, --overwrite | When set, any settings in your configuration file which are present as environment variables in the current session will be overwritten. | -o | | -s, --disable-spinner | When set, disables inline spinner | -s | | --prepare-dev | When set, will create the `.env` file based on the current configuration that is required to run a separate client development server. | --prepare-dev | -| --experiment-visibility-control | When set, will create the `.env` file based on the current configuration that is required to run a separate client development server. | --experiment-visibility-control| In the repository, you will see two docker directories: `docker` and `docker-local`. The main deployment will use the `docker` directory. The `docker-local` directory is a separate local version of Loon which we will discuss shortly. Below are some examples. diff --git a/apps/client/src/App.vue b/apps/client/src/App.vue index 703eb7d8..faf2ba18 100644 --- a/apps/client/src/App.vue +++ b/apps/client/src/App.vue @@ -7,9 +7,12 @@ import { useProvenanceStore } from '@/stores/misc/provenanceStore'; import { onKeyStroke } from '@vueuse/core'; import { router } from '@/router'; import LBtn from './components/custom/LBtn.vue'; +import LoginModal from './components/auth/LoginModal.vue'; +import { useAuthStore } from '@/stores/misc/authStore'; const $q = useQuasar(); const provenanceStore = useProvenanceStore(); +const authStore = useAuthStore(); onKeyStroke(['z', 'Z'], (e: KeyboardEvent) => { if (globalSettings.usingMac && !e.metaKey) return; @@ -26,6 +29,8 @@ onKeyStroke(['z', 'Z'], (e: KeyboardEvent) => { }); const globalSettings = useGlobalSettings(); +const showLoginModal = ref(false); + watch( () => globalSettings.darkMode, (newDarkMode: boolean) => { @@ -71,8 +76,25 @@ onBeforeMount(() => { type="basic" label="Upload" /> + + + diff --git a/apps/client/src/components/auth/LoginModal.vue b/apps/client/src/components/auth/LoginModal.vue new file mode 100644 index 00000000..6377de5e --- /dev/null +++ b/apps/client/src/components/auth/LoginModal.vue @@ -0,0 +1,197 @@ + + + diff --git a/apps/client/src/pages/AdminPage.vue b/apps/client/src/pages/AdminPage.vue new file mode 100644 index 00000000..2c9be824 --- /dev/null +++ b/apps/client/src/pages/AdminPage.vue @@ -0,0 +1,276 @@ + + + diff --git a/apps/client/src/router/index.ts b/apps/client/src/router/index.ts index ade56116..d837897f 100644 --- a/apps/client/src/router/index.ts +++ b/apps/client/src/router/index.ts @@ -17,6 +17,10 @@ const routes: RouteRecordRaw[] = [ path: '/upload', component: UploadPage, }, + { + path: '/admin', + component: () => import('../pages/AdminPage.vue'), + }, // Always leave this as last one, // but you can also remove it { diff --git a/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts b/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts index a3cbf797..10f5ec1e 100644 --- a/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts +++ b/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts @@ -8,6 +8,7 @@ import { computedAsync } from '@vueuse/core'; import { useCellMetaData } from '@/stores/dataStores/cellMetaDataStore'; import { useDatasetSelectionTrrackedStore } from '@/stores/dataStores/datasetSelectionTrrackedStore'; import { useConfigStore } from '../misc/configStore'; +import { useAuthStore } from '../misc/authStore'; import type { TextTransforms } from '@/util/datasetLoader'; import type { SelectedLocationIds } from '@/stores/dataStores/datasetSelectionTrrackedStore'; @@ -99,6 +100,11 @@ export const useDatasetSelectionStore = defineStore( const route = useRoute(); const experimentFilenameList = asyncComputed(async () => { if (configStore.serverUrl == null) return []; + + // Re-trigger computed whenever current user login state changes + const authStore = useAuthStore(); + const currentUser = authStore.currentUser; + const fullURL = configStore.getFileUrl( configStore.entryPointFilename ); @@ -136,36 +142,24 @@ export const useDatasetSelectionStore = defineStore( // Initialize Duckdb Socket Connection vg.coordinator().databaseConnector(connector); - + const data = await response.json(); - const rawExperiments: (string | { filename: string; 'visible-by-default'?: boolean })[] = data.experiments; - - // Normalize entries: extract filename from objects, pass strings through - const allFilenames = rawExperiments.map((entry) => - typeof entry === 'string' ? entry : entry.filename - ); - - // If experiment visibility control is not enabled, show all experiments - const visibilityControlEnabled = - import.meta.env.VITE_EXPERIMENT_VISIBILITY_CONTROL === 'true'; - if (!visibilityControlEnabled) { - return allFilenames; - } - - // If show-all-experiments URL param is present, show all - if ('show-all-experiments' in route.query) { - return allFilenames; - } - - // Filter to only visible-by-default experiments + const rawExperiments = (data.experiments || []) as (string | { filename: string; users?: string[] })[]; + return rawExperiments - .filter((entry) => - typeof entry === 'object' && entry['visible-by-default'] === true - ) + .filter((entry) => { + // Plain string entries (no gating metadata) → always visible + if (typeof entry === 'string') return true; + // Admin sees everything + if (currentUser === 'admin') return true; + // Object entries: only visible if user is in the users list + if (!entry.users || entry.users.length === 0) return false; + return currentUser != null && entry.users.includes(currentUser); + }) .map((entry) => typeof entry === 'string' ? entry : entry.filename ); - }, [refreshTime.value]); + }, []); // Sets location Metadata const currentExperimentMetadata = diff --git a/apps/client/src/stores/misc/authStore.ts b/apps/client/src/stores/misc/authStore.ts new file mode 100644 index 00000000..5deb7900 --- /dev/null +++ b/apps/client/src/stores/misc/authStore.ts @@ -0,0 +1,69 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { useConfigStore } from './configStore'; + +export const useAuthStore = defineStore('authStore', () => { + const isInitialized = ref(false); + const currentUser = ref(null); + const currentPassword = ref(null); + const users = ref([]); + + async function fetchUsers() { + const configStore = useConfigStore(); + if (!configStore.apiUrl) return; + + try { + const response = await fetch(`${configStore.apiUrl}/users/`); + if (response.ok) { + const data = await response.json(); + if (data.users && Array.isArray(data.users)) { + users.value = data.users.filter((u: string) => u !== 'admin'); + } + } + } catch (e) { + console.error('Failed to fetch users', e); + } finally { + isInitialized.value = true; + } + } + + async function initLogins() { + if (isInitialized.value) return; + await fetchUsers(); + } + + async function login(username: string, pass: string): Promise { + const configStore = useConfigStore(); + try { + const response = await fetch(`${configStore.apiUrl}/login/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password: pass }) + }); + if (response.ok) { + currentUser.value = username; + currentPassword.value = pass; + return true; + } + } catch (e) { + console.error('Login error', e); + } + return false; + } + + function logout() { + currentUser.value = null; + currentPassword.value = null; + } + + return { + isInitialized, + currentUser, + currentPassword, + users, + initLogins, + fetchUsers, + login, + logout + }; +}); diff --git a/apps/client/src/stores/misc/configStore.ts b/apps/client/src/stores/misc/configStore.ts index d581bb57..e8b66a16 100644 --- a/apps/client/src/stores/misc/configStore.ts +++ b/apps/client/src/stores/misc/configStore.ts @@ -39,7 +39,12 @@ export const useConfigStore = defineStore('configStore', () => { '/data', '/ws/' )}`; - const apiUrl = `${httpValue}${envServerUrl.replace('/data', '/api')}`; + + let apiUrl = `${httpValue}${envServerUrl.replace('/data', '/api')}`; + if (environment === 'local') { + const hostOnly = envServerUrl.split(':')[0]; + apiUrl = `${httpValue}${hostOnly}:8000/api`; + } // Environment based location of data retrieval for DuckDb diff --git a/apps/docs-website/docs/getting-started-with-loon/data.md b/apps/docs-website/docs/getting-started-with-loon/data.md index 36276631..507eb73b 100644 --- a/apps/docs-website/docs/getting-started-with-loon/data.md +++ b/apps/docs-website/docs/getting-started-with-loon/data.md @@ -83,16 +83,16 @@ This removes the time burden of uploading to your server, however! > > #### `aa_index.json` File Example: > +> Experiments can be listed as a simple string (publicly visible) or an object with a `users` array (visible only to those users and administrators). +> > ```json > { > "experiments": [ -> "experiment_1.json", -> "experiment_2.json", -> "experiment_3.json", -> "experiment_4.json", -> "experiment_5.json", -> "experiment_6.json", -> "experiment_7.json", +> { +> "filename": "restricted_experiment.json", +> "users": ["lab", "jess"] +> }, +> "public_experiment.json" > ] > } > ``` diff --git a/apps/server/api/views.py b/apps/server/api/views.py index 56f758a6..abeb3518 100644 --- a/apps/server/api/views.py +++ b/apps/server/api/views.py @@ -216,12 +216,27 @@ def post(self, request): experiment_names = [name + '.json' for name in Experiment.objects.values_list('name', flat=True)] - index_file = {"experiments": experiment_names} + index_file_name = 'aa_index.json' + + existing_permissions = {} + if default_storage.exists(index_file_name): + with default_storage.open(index_file_name, 'r') as f: + content = json.loads(f.read()) + for exp in content.get('experiments', []): + if isinstance(exp, dict) and 'filename' in exp: + existing_permissions[exp['filename']] = exp.get('users', []) + + experiments_data = [] + for name in experiment_names: + if name in existing_permissions: + experiments_data.append({"filename": name, "users": existing_permissions[name]}) + else: + experiments_data.append(name) + + index_file = {"experiments": experiments_data} json_index_file = json.dumps(index_file, indent=4) json_index_file_bytes = json_index_file.encode('utf-8') - index_file_name = 'aa_index.json' - if default_storage.exists(index_file_name): default_storage.delete(index_file_name) @@ -269,3 +284,87 @@ def strip_json(s): return Response({'status': 'FAILED'}) return Response({'status': 'SUCCESS'}) + +from django.conf import settings # type: ignore + +PASSWORDS_FILE = os.path.join(settings.BASE_DIR, 'passwords.json') + +def load_passwords(): + if os.path.exists(PASSWORDS_FILE): + with open(PASSWORDS_FILE, 'r') as f: + return json.load(f) + return {"admin": "admin"} + +class LoginAuthView(APIView): + def post(self, request): + username = request.data.get('username') + password = request.data.get('password') + passwords = load_passwords() + + if passwords.get(username) == password: + return Response({'status': 'SUCCESS'}) + return Response({'status': 'FAILED'}, status=401) + +class ListUsersView(APIView): + def get(self, request): + passwords = load_passwords() + users = list(passwords.keys()) + return Response({'users': users}) + +class AdminDataView(APIView): + def post(self, request): + username = request.data.get('username') + password = request.data.get('password') + passwords = load_passwords() + if username != 'admin' or passwords.get('admin') != password: + return Response({'status': 'UNAUTHORIZED'}, status=401) + + aa_index = {"experiments": []} + if default_storage.exists('aa_index.json'): + with default_storage.open('aa_index.json', 'r') as file: + aa_index = json.loads(file.read()) + + return Response({ + 'users': passwords, + 'aa_index': aa_index + }) + +class AdminUpdateView(APIView): + def post(self, request): + username = request.data.get('username') + password = request.data.get('password') + passwords = load_passwords() + if username != 'admin' or passwords.get('admin') != password: + return Response({'status': 'UNAUTHORIZED'}, status=401) + + new_users_dict = request.data.get('users', {}) + new_experiment_permissions = request.data.get('permissions', {}) + + if 'admin' not in new_users_dict: + new_users_dict['admin'] = passwords.get('admin', 'admin') + + with open(PASSWORDS_FILE, 'w') as f: + json.dump(new_users_dict, f, indent=4) + + index_file_name = 'aa_index.json' + if default_storage.exists(index_file_name): + with default_storage.open(index_file_name, 'r') as f: + content = json.loads(f.read()) + + new_experiments = [] + for exp in content.get('experiments', []): + filename = exp['filename'] if isinstance(exp, dict) else exp + if filename in new_experiment_permissions: + new_experiments.append({ + "filename": filename, + "users": new_experiment_permissions[filename] + }) + else: + new_experiments.append(filename) + + index_file = {"experiments": new_experiments} + json_index_file_bytes = json.dumps(index_file, indent=4).encode('utf-8') + default_storage.delete(index_file_name) + default_storage.save(index_file_name, ContentFile(json_index_file_bytes)) + + return Response({'status': 'SUCCESS'}) diff --git a/apps/server/debug_data.py b/apps/server/debug_data.py new file mode 100644 index 00000000..b2b7b829 --- /dev/null +++ b/apps/server/debug_data.py @@ -0,0 +1,30 @@ +import os +import json +from django.conf import settings +from django.core.files.storage import default_storage + +def check_data(): + print(f"MEDIA_ROOT: {settings.MEDIA_ROOT}") + exists = default_storage.exists('aa_index.json') + print(f"aa_index.json exists: {exists}") + if exists: + with default_storage.open('aa_index.json', 'r') as f: + content = f.read() + print("Content (first 100 chars):", content[:100]) + try: + data = json.loads(content) + print("Experiments count:", len(data.get('experiments', []))) + except Exception as e: + print("JSON mismatch:", e) + + # Check directory listing + try: + subdirs, files = default_storage.listdir('') + print("Files in root:", files) + except Exception as e: + print("Listdir error:", e) + +if __name__ == "__main__": + import django + django.setup() + check_data() diff --git a/apps/server/passwords.json b/apps/server/passwords.json new file mode 100644 index 00000000..b492af7a --- /dev/null +++ b/apps/server/passwords.json @@ -0,0 +1,6 @@ +{ + "admin": "admin", + "lab": "password123", + "jess": "jesspass", + "mike": "mikepass" +} diff --git a/apps/server/passwords.json.example b/apps/server/passwords.json.example new file mode 100644 index 00000000..094017c4 --- /dev/null +++ b/apps/server/passwords.json.example @@ -0,0 +1,3 @@ +{ + "admin": "admin" +} diff --git a/apps/server/server/settings.py b/apps/server/server/settings.py index bd175e88..bbc2df52 100644 --- a/apps/server/server/settings.py +++ b/apps/server/server/settings.py @@ -40,7 +40,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool('DEBUG', default=True) -ALLOWED_HOSTS = [env('ALLOWED_HOST', default='')] +ALLOWED_HOSTS = [env('ALLOWED_HOST', default=''), 'localhost:8000', '127.0.0.1:8000', 'localhost', '127.0.0.1'] MINIO_ENABLED = env.bool('MINIO_ENABLED', default=True) @@ -65,6 +65,7 @@ INSTALLED_APPS.append("minio_storage") MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -72,7 +73,6 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "corsheaders.middleware.CorsMiddleware", ] ROOT_URLCONF = "server.urls" @@ -171,3 +171,5 @@ MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True MINIO_STORAGE_MEDIA_URL = SITE_PREFIX + env('MINIO_STORAGE_MEDIA_URL') MINIO_STORAGE_STATIC_URL = SITE_PREFIX + env('MINIO_STORAGE_STATIC_URL') +else: + MEDIA_ROOT = '/app/data' diff --git a/apps/server/server/urls.py b/apps/server/server/urls.py index f768e877..c1b5b8c1 100644 --- a/apps/server/server/urls.py +++ b/apps/server/server/urls.py @@ -17,7 +17,15 @@ from django.contrib import admin # type: ignore from django.urls import path, include # type: ignore -from api.views import FinishExperimentView, ProcessDataView, VerifyExperimentNameView +from api.views import ( + FinishExperimentView, + ProcessDataView, + VerifyExperimentNameView, + LoginAuthView, + ListUsersView, + AdminDataView, + AdminUpdateView +) urlpatterns = [ path("admin/", admin.site.urls), @@ -28,5 +36,9 @@ path('api/verifyExperimentName/', VerifyExperimentNameView.as_view(), name="verify-experiment-name" - ) + ), + path('api/login/', LoginAuthView.as_view(), name="login-auth"), + path('api/users/', ListUsersView.as_view(), name="list-users"), + path('api/admin/data/', AdminDataView.as_view(), name="admin-data"), + path('api/admin/update/', AdminUpdateView.as_view(), name="admin-update"), ] diff --git a/build.py b/build.py index 9642ff11..0ab369de 100644 --- a/build.py +++ b/build.py @@ -81,7 +81,7 @@ def createComposeFile(local=False, nfs=False): shutil.copy(docker_compose_template, '.build-files/docker-compose.yml') -def createEnvFile(configFileName, envFileName, useDid=False, experimentVisibilityControl=False): +def createEnvFile(configFileName, envFileName, useDid=False): buildConfig = BuildConfig.BuildConfig(configFileName, envFileName) buildConfig.reportErrors() @@ -110,7 +110,6 @@ def createEnvFile(configFileName, envFileName, useDid=False, experimentVisibilit buildConfig.set('VITE_ENVIRONMENT', environment) buildConfig.set('VITE_SERVER_URL', f'{base_url}/data') - buildConfig.set('VITE_EXPERIMENT_VISIBILITY_CONTROL', str(experimentVisibilityControl).lower()) localPort1 = buildConfig.get("generalSettings.local_port_1") buildConfig.set("LOCAL_PORT_1", localPort1) @@ -391,16 +390,14 @@ def cleanup_and_exit(signal=None, frame=None): sys.exit(0) -def prepare_dev(buildConfig, experimentVisibilityControl=False): +def prepare_dev(buildConfig): vite_server_url = f"{buildConfig.get('generalSettings.baseUrl')}/data" vite_use_http = f"{buildConfig.get('generalSettings.useHttp')}" vite_environment = f"{buildConfig.get('generalSettings.environment')}" - vite_experiment_visibility_control = str(experimentVisibilityControl).lower() outString = f"VITE_SERVER_URL={vite_server_url}\n" \ f"VITE_USE_HTTP={vite_use_http}\n" \ - f"VITE_ENVIRONMENT={vite_environment}\n" \ - f"VITE_EXPERIMENT_VISIBILITY_CONTROL={vite_experiment_visibility_control}\n" + f"VITE_ENVIRONMENT={vite_environment}\n" fullEnvFileName = 'apps/client/.env' with open(fullEnvFileName, 'w') as outF: @@ -448,10 +445,7 @@ def spinner(msg): help="Generates .env file for client environment.") parser.add_argument("-i", "--use-did", action="store_true", required=False, help="Flag to specify that this build script is running in a DiD setup.") - parser.add_argument("--experiment-visibility-control", action="store_true", required=False, - help="Enable experiment visibility control. Only experiments with " - "'visible-by-default' in aa_index.json will be shown unless " - "'show-all-experiments' URL param is used.") + args = parser.parse_args() @@ -471,7 +465,7 @@ def spinner(msg): print('No client .env file found.') pass - buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did, args.experiment_visibility_control) + buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did) use_http = buildConfig.get('generalSettings.useHttp') if use_http: @@ -520,7 +514,7 @@ def spinner(msg): follow_all_logs(logs_path, services, args.verbose, args.detached) if args.prepare_dev: - prepare_dev(buildConfig, args.experiment_visibility_control) + prepare_dev(buildConfig) check_containers_status(services, args.detached) else: @@ -529,5 +523,5 @@ def spinner(msg): if args.overwrite: config_file_name = overwrite_config(config_file_name) - buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did, args.experiment_visibility_control) + buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did) createComposeFile(local=buildConfig.local, nfs=buildConfig.nfs)