diff --git a/.dockerignore b/.dockerignore index 490ecaf..0e563d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,5 +7,5 @@ matching.db csv *.png *.xlsx -*.txt +gitea-api.txt data diff --git a/Dockerfile b/Dockerfile index 9366173..9c28153 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,33 @@ FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - PORT=8090 + PORT=8091 \ + CHROME_PATH=/usr/bin/chromium WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + chromium \ + fonts-noto-cjk \ + nodejs \ + npm \ + && rm -rf /var/lib/apt/lists/* + COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt -COPY app.py index.html detail-view.html detail-view-project.html ./ +RUN npm install --omit=dev playwright-core -# matching.db는 볼륨으로 마운트해서 데이터 보존 -VOLUME ["/app/data"] +COPY mysql_preview_server.py \ + index.html \ + detail-view.html \ + detail-view-project.html \ + mysql-preview.html \ + people-unified.html \ + project-codes.html \ + ./ -EXPOSE 8090 +EXPOSE 8091 8092 -CMD ["sh", "-c", "ln -sf /app/data/matching.db /app/matching.db 2>/dev/null || true; python3 app.py"] +CMD ["python3", "mysql_preview_server.py"] diff --git a/docker-compose.yml b/docker-compose.yml index 79e85ce..2dee689 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,12 @@ -version: "3.9" - services: - manhour-dashboard: + jh-people-8091: build: . - container_name: manhour-dashboard + container_name: jh-people-8091 ports: - - "8090:8090" + - "8091:8091" environment: - PORT: "8090" + PORT: "8091" + STARTUP_MAINTENANCE: "1" MYSQL_HOST: "${MYSQL_HOST:-172.16.42.111}" MYSQL_PORT: "${MYSQL_PORT:-3306}" MYSQL_USER: "${MYSQL_USER:-root}" @@ -20,5 +19,30 @@ services: INTRANET_LOGIN_ID: "${INTRANET_LOGIN_ID:-G25001}" INTRANET_LOGIN_PW: "${INTRANET_LOGIN_PW:-00000}" volumes: - - ./data:/app/data + - ./matching.db:/app/matching.db + restart: unless-stopped + + jh-project-8092: + build: . + container_name: jh-project-8092 + ports: + - "8092:8092" + environment: + PORT: "8092" + STARTUP_MAINTENANCE: "0" + MYSQL_HOST: "${MYSQL_HOST:-172.16.42.111}" + MYSQL_PORT: "${MYSQL_PORT:-3306}" + MYSQL_USER: "${MYSQL_USER:-root}" + MYSQL_PASSWORD: "${MYSQL_PASSWORD:-hanmacerp!}" + MYSQL_DB: "${MYSQL_DB:-jangheon_manhour}" + ERP_BASE_URL: "${ERP_BASE_URL:-http://erp.jangheon.co.kr/projt_mng}" + ERP_LOGIN_ID: "${ERP_LOGIN_ID:-g25001}" + ERP_LOGIN_PW: "${ERP_LOGIN_PW:-00000}" + INTRANET_BASE_URL: "${INTRANET_BASE_URL:-http://erp.jangheon.co.kr/intranet/}" + INTRANET_LOGIN_ID: "${INTRANET_LOGIN_ID:-G25001}" + INTRANET_LOGIN_PW: "${INTRANET_LOGIN_PW:-00000}" + volumes: + - ./matching.db:/app/matching.db + depends_on: + - jh-people-8091 restart: unless-stopped diff --git a/matching.db b/matching.db index ff7ac70..5aaaf48 100644 Binary files a/matching.db and b/matching.db differ diff --git a/mysql_preview_server.py b/mysql_preview_server.py index e7f059c..9e1a03d 100644 --- a/mysql_preview_server.py +++ b/mysql_preview_server.py @@ -439,9 +439,10 @@ def resolve_naver_address_token(address): const { chromium } = require('playwright-core'); const address = process.argv[1]; (async () => { + const executablePath = process.env.CHROME_PATH || '/usr/bin/google-chrome'; const browser = await chromium.launch({ headless: true, - executablePath: '/usr/bin/google-chrome', + executablePath, args: ['--no-sandbox', '--disable-dev-shm-usage'], }); const page = await browser.newPage(); @@ -472,8 +473,13 @@ const address = process.argv[1]; }); """ env = os.environ.copy() - npx_node_path = '/home/hyein/.npm/_npx/9833c18b2d85bc59/node_modules' - env['NODE_PATH'] = f"{npx_node_path}:{env.get('NODE_PATH', '')}" if env.get('NODE_PATH') else npx_node_path + node_paths = [ + os.path.join(BASE_DIR, 'node_modules'), + '/home/hyein/.npm/_npx/9833c18b2d85bc59/node_modules', + ] + existing_node_paths = [path for path in node_paths if os.path.isdir(path)] + if existing_node_paths: + env['NODE_PATH'] = ':'.join(existing_node_paths + ([env['NODE_PATH']] if env.get('NODE_PATH') else [])) try: completed = subprocess.run( ['node', '-e', node_script, cleaned], @@ -4583,24 +4589,27 @@ if __name__ == '__main__': os.makedirs(BASE_DIR, exist_ok=True) with open_db_connection() as conn: init_db(conn) - try: - alias_rows = load_project_alias_from_erp() - print(f'Loaded project aliases from ERP: {len(alias_rows)}') - replace_project_alias(conn, alias_rows) - except Exception as e: - print(f'Load project aliases from ERP failed, keep existing aliases: {e}') - # 직급은 MySQL member.rankCode -> systemconfig 매핑 기반으로 /api/load 시 반영 - try: - fixed = backfill_daily_hours_if_needed(conn) - if fixed: - print(f'Backfilled hour columns for {fixed} rows.') - rebuilt = rebuild_work_calendar_tables(conn) - print(f"Rebuilt work calendar tables: days={rebuilt['days']}, details={rebuilt['details']}") - except sqlite3.OperationalError as e: - if 'locked' in str(e).lower(): - print(f'Skip startup rebuild because database is locked: {e}') - else: - raise + if os.environ.get('STARTUP_MAINTENANCE', '1') not in ('0', 'false', 'False', 'no'): + try: + alias_rows = load_project_alias_from_erp() + print(f'Loaded project aliases from ERP: {len(alias_rows)}') + replace_project_alias(conn, alias_rows) + except Exception as e: + print(f'Load project aliases from ERP failed, keep existing aliases: {e}') + # 직급은 MySQL member.rankCode -> systemconfig 매핑 기반으로 /api/load 시 반영 + try: + fixed = backfill_daily_hours_if_needed(conn) + if fixed: + print(f'Backfilled hour columns for {fixed} rows.') + rebuilt = rebuild_work_calendar_tables(conn) + print(f"Rebuilt work calendar tables: days={rebuilt['days']}, details={rebuilt['details']}") + except sqlite3.OperationalError as e: + if 'locked' in str(e).lower(): + print(f'Skip startup rebuild because database is locked: {e}') + else: + raise + else: + print('Skip startup maintenance by STARTUP_MAINTENANCE=0') port = int(os.environ.get('PORT', '8091')) host = '0.0.0.0'