Compare commits
67 Commits
c6515c1b5d
...
thoon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8259bfa75 | ||
|
|
b5d18b6839 | ||
|
|
615523fb90 | ||
|
|
10e27c0096 | ||
|
|
578196d9d4 | ||
|
|
dea6beee0b | ||
|
|
8129f85071 | ||
|
|
87459c8f44 | ||
|
|
4d98d9a48e | ||
|
|
1da75e4abd | ||
|
|
3e26420945 | ||
|
|
8e22c1d713 | ||
|
|
8747b3946f | ||
|
|
ed3d8812c2 | ||
|
|
5588fae6f9 | ||
|
|
e6afe2b6d3 | ||
|
|
9049b60ee5 | ||
|
|
a5c4a15fab | ||
|
|
1ab59bc9e1 | ||
|
|
7389ed2d82 | ||
|
|
a73dd76e70 | ||
|
|
cbfc1bcd1d | ||
|
|
6ed939c6bf | ||
|
|
1ecee53966 | ||
|
|
322a8ae882 | ||
|
|
8176180e52 | ||
|
|
2137ee364c | ||
|
|
afd89322bb | ||
|
|
1457bf277f | ||
|
|
0bfff08af6 | ||
| ae1fd4b121 | |||
|
|
1eca0ede91 | ||
|
|
f36e8e93e2 | ||
|
|
9f165faf13 | ||
|
|
577f138533 | ||
|
|
237ac9ee25 | ||
|
|
aacd2fe7db | ||
|
|
90403a1acd | ||
|
|
6a76f6968b | ||
|
|
621b05a890 | ||
| 7b631ab858 | |||
| 9735344f37 | |||
| 67e3be028b | |||
| 58f93c959d | |||
| 4231acc691 | |||
| f41f2378d7 | |||
| 662f720c6a | |||
| 5678e28c66 | |||
| 41406f56e8 | |||
| 15c5cbaca2 | |||
| 84d35c1409 | |||
| 07eb48f27c | |||
| fb45c38107 | |||
| 6c21e4816e | |||
| af578a63bc | |||
| e8bc42e5de | |||
| 587e92a7da | |||
| e208e52ed9 | |||
| 5dbf69e963 | |||
| d771b28d88 | |||
| 6848baae5f | |||
| a0570e88d4 | |||
| 502e5059b7 | |||
| d54997cd55 | |||
| fa8dec1780 | |||
| 9d19d8283e | |||
| b9d28736e2 |
31
.agents/AGENTS.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Development Rules (개발 및 관리 규칙)
|
||||
|
||||
1. **언어 설정**: 영어로 생각하되, 모든 답변은 **한국어**로 작성한다.
|
||||
2. **임의 수정 절대 금지 (Zero-Arbitrary Change)**:
|
||||
- 사용자가 명시적으로 지시한 부분 외에는 **단 한 줄의 코드도, 그 어떤 파일도 임의로 수정, 정리, 리팩토링하지 않는다.**
|
||||
- 지시받지 않은 다른 파트의 코드는 절대 건드리지 않으며, 영향 범위가 요청 범위를 벗어나지 않도록 '외과 수술식(Surgical) 수정'을 원칙으로 한다.
|
||||
3. **개선 작업 절차 (Test-First Approach)**:
|
||||
- 사용자가 개선(Refactoring, Optimization 등)을 지시한 경우, **수정 전 현재 시스템이 정상적으로 잘 작동하는지 먼저 전수 확인**한다.
|
||||
- 기존 동작 방식과 성능을 기준(Baseline)으로 삼고, 수정 후에도 **기존의 모든 기능이 무결하게 유지되는지 반드시 테스트하여 입증**한다.
|
||||
- 검증 결과를 바탕으로 "무엇을, 왜, 어떻게" 바꿀지 상세 보고 후, 사용자로부터 **'진행시켜'** 승인을 얻은 뒤에만 집행한다.
|
||||
4. **선보고 후승인**: 모든 기능 수정 및 코드 변경 전에는 예상 방안을 먼저 보고하고 승인 절차를 거친다.
|
||||
5. **DB 삭제 및 초기화 절대 엄금 (Strict DB Deletion Policy)**:
|
||||
- 어떠한 경우에도 `DELETE`, `DROP`, `TRUNCATE` 등 데이터를 삭제하거나 테이블을 초기화하는 작업은 사전에 사용자에게 상세 사유를 보고하고 **명시적 승인**을 얻은 후에만 시행한다.
|
||||
- 기존 데이터의 가치를 최우선으로 하며, 작업 전 백업 여부를 반드시 확인한다.
|
||||
6. **RED–GREEN–Refactor 개발 원칙**:
|
||||
- 모든 기능 개발과 버그 수정은 **RED → GREEN → Refactor** 순서로 진행한다.
|
||||
- **RED**: 요구사항을 명확히 표현하는 테스트를 먼저 작성하고, 해당 테스트가 기능 미구현 또는 결함으로 인해 실패하는지 확인한다.
|
||||
- **GREEN**: 실패한 테스트를 통과시키는 데 필요한 최소한의 코드만 구현하며, 불필요한 기능 추가나 구조 변경을 하지 않는다.
|
||||
- **Refactor**: 관련 테스트와 기존 테스트가 모두 통과하는 상태에서만 중복 제거, 명칭 개선, 책임 분리 등 코드 구조를 개선하며 동작은 변경하지 않는다.
|
||||
- 각 단계가 끝날 때마다 관련 테스트와 기존 기능의 회귀 여부를 검증한다.
|
||||
- 테스트 작성이 현실적으로 불가능한 경우에는 그 사유와 대체 검증 방법을 먼저 보고하고 승인을 받은 후 진행한다.
|
||||
- 본 원칙을 적용할 때에도 기존의 **선보고 후승인** 및 **외과 수술식 수정** 규칙을 준수한다.
|
||||
|
||||
# Server Run & External Access (서버 구동 및 외부 접속 규칙)
|
||||
|
||||
1. **포트 고정**: 개발 서버는 반드시 **8080** 포트를 사용한다. (`vite.config.ts` 설정 준수)
|
||||
2. **외부 접속 허용 (Host)**: 사무실 내 타 직원이 접속할 수 있도록 `--host` 모드로 구동한다.
|
||||
3. **구동 명령어**: `npm run dev`
|
||||
- 해당 명령어 실행 시 `0.0.0.0` 또는 `Network: http://[내-IP]:8080/` 경로로 타인 접속이 가능하다.
|
||||
4. **IP 확인 방법**:
|
||||
- Windows: `ipconfig` 명령어로 'IPv4 주소' 확인 후 공유.
|
||||
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
npm-debug.log
|
||||
uploads
|
||||
*.xlsx
|
||||
*.log
|
||||
mysql_data
|
||||
scratch
|
||||
*.sql
|
||||
4
.env
@@ -1,6 +1,6 @@
|
||||
DB_HOST=172.16.8.151
|
||||
DB_HOST=itam-mysql
|
||||
DB_PORT=3306
|
||||
DB_USER=itam_admin
|
||||
DB_USER=itam
|
||||
DB_PASS=itam1234
|
||||
DB_NAME=itam
|
||||
PORT=3000
|
||||
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# Database Configuration
|
||||
DB_HOST=172.16.8.151
|
||||
DB_PORT=3306
|
||||
DB_USER=itam_admin
|
||||
DB_PASS=itam1234
|
||||
DB_NAME=itam
|
||||
|
||||
# Application Configuration
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
|
||||
# Logging (optional)
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Security (for production)
|
||||
# API_KEY=your_api_key_here
|
||||
# JWT_SECRET=your_jwt_secret_here
|
||||
7
.gitea/coverage.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Path": "./backend/coverage.out",
|
||||
"Thresholds": {
|
||||
"baron-sso-backend/internal/handler": 10,
|
||||
"baron-sso-backend/internal/service": 10
|
||||
}
|
||||
}
|
||||
47
.gitea/workflows/itam_code_check.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
name: ITAM Code Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- Dockerizing
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-config-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare CI env file
|
||||
run: |
|
||||
cat <<'EOF' > .env
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=itam_ci
|
||||
DB_PASS=itam_ci_password
|
||||
DB_NAME=itam
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
LOG_LEVEL=info
|
||||
EOF
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Frontend TypeScript and Vite build
|
||||
run: npm run build
|
||||
|
||||
- name: Validate test compose
|
||||
run: docker compose -f docker-compose.test.yaml config
|
||||
|
||||
- name: Validate prod compose
|
||||
run: docker compose -f docker-compose.prod.yaml config
|
||||
69
.gitea/workflows/itam_docker_build_check.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
name: ITAM Docker Build Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- Dockerizing
|
||||
- main
|
||||
paths:
|
||||
- "Dockerfile.frontend.prod"
|
||||
- "Dockerfile.backend.prod"
|
||||
- "docker-compose.prod.yaml"
|
||||
- "docker-compose.test.yaml"
|
||||
- "docker/**"
|
||||
- "src/**"
|
||||
- "server.js"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- "vite.config.ts"
|
||||
- "index.html"
|
||||
- "img/**"
|
||||
- "public/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "Dockerfile.frontend.prod"
|
||||
- "Dockerfile.backend.prod"
|
||||
- "docker-compose.prod.yaml"
|
||||
- "docker-compose.test.yaml"
|
||||
- "docker/**"
|
||||
- "src/**"
|
||||
- "server.js"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- "vite.config.ts"
|
||||
- "index.html"
|
||||
- "img/**"
|
||||
- "public/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
docker-build-check:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
COMPOSE_DOCKER_CLI_BUILD: "1"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare CI env file
|
||||
run: |
|
||||
cat <<'EOF' > .env
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=itam_ci
|
||||
DB_PASS=itam_ci_password
|
||||
DB_NAME=itam
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
LOG_LEVEL=info
|
||||
EOF
|
||||
|
||||
- name: Build backend production image
|
||||
run: docker build -f Dockerfile.backend.prod -t itam-backend:ci .
|
||||
|
||||
- name: Build frontend production image
|
||||
run: docker build -f Dockerfile.frontend.prod -t itam-frontend:ci .
|
||||
|
||||
- name: Validate production compose with CI env
|
||||
run: docker compose -f docker-compose.prod.yaml config
|
||||
143
.gitea/workflows/itam_production_deploy.yml
Normal file
@@ -0,0 +1,143 @@
|
||||
name: ITAM Production Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_branch:
|
||||
description: "Branch to deploy"
|
||||
required: true
|
||||
default: "main"
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy-production:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH agent
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Validate required production variables
|
||||
env:
|
||||
PROD_HOST: ${{ vars.PROD_HOST }}
|
||||
PROD_USER: ${{ vars.PROD_USER }}
|
||||
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
||||
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
|
||||
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
||||
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
||||
DB_USER: ${{ vars.PROD_DB_USER }}
|
||||
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
||||
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
required_keys="PROD_HOST PROD_USER PROD_DEPLOY_PATH PROD_GIT_URL DB_HOST DB_PORT DB_USER DB_PASS DB_NAME"
|
||||
for key in ${required_keys}; do
|
||||
if [ -z "${!key:-}" ]; then
|
||||
echo "::error::Missing required variable or secret: ${key}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create production env file
|
||||
env:
|
||||
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
||||
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
||||
DB_USER: ${{ vars.PROD_DB_USER }}
|
||||
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
||||
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
||||
LOG_LEVEL: ${{ vars.PROD_LOG_LEVEL }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EFFECTIVE_LOG_LEVEL="${LOG_LEVEL:-info}"
|
||||
cat > .env.deploy <<EOF
|
||||
DB_HOST=${DB_HOST}
|
||||
DB_PORT=${DB_PORT}
|
||||
DB_USER=${DB_USER}
|
||||
DB_PASS=${DB_PASS}
|
||||
DB_NAME=${DB_NAME}
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
LOG_LEVEL=${EFFECTIVE_LOG_LEVEL}
|
||||
EOF
|
||||
|
||||
- name: Deploy to production host
|
||||
env:
|
||||
PROD_HOST: ${{ vars.PROD_HOST }}
|
||||
PROD_USER: ${{ vars.PROD_USER }}
|
||||
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
||||
PROD_BACKUP_ROOT: ${{ vars.PROD_BACKUP_ROOT }}
|
||||
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
|
||||
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
||||
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
||||
DB_USER: ${{ vars.PROD_DB_USER }}
|
||||
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
||||
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
||||
TARGET_BRANCH: ${{ github.event.inputs.target_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ssh-keyscan -H "${PROD_HOST}" >> ~/.ssh/known_hosts
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "mkdir -p '${PROD_DEPLOY_PATH}'"
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "if [ ! -d '${PROD_DEPLOY_PATH}/.git' ]; then git clone '${PROD_GIT_URL}' '${PROD_DEPLOY_PATH}'; else cd '${PROD_DEPLOY_PATH}' && git remote set-url origin '${PROD_GIT_URL}'; fi"
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && git checkout -- .env || true && git fetch origin '${TARGET_BRANCH}' && git checkout -B '${TARGET_BRANCH}' FETCH_HEAD && git reset --hard FETCH_HEAD"
|
||||
|
||||
EFFECTIVE_BACKUP_ROOT="${PROD_BACKUP_ROOT:-/home/user/dachs_backups}"
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "export DEPLOY_PATH='${PROD_DEPLOY_PATH}' BACKUP_ROOT='${EFFECTIVE_BACKUP_ROOT}'; sh -eu -s" <<'REMOTE_BACKUP'
|
||||
case "$BACKUP_ROOT" in
|
||||
"$DEPLOY_PATH"|"$DEPLOY_PATH"/*)
|
||||
echo "Backup path must be outside deploy path: $BACKUP_ROOT"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -d "$DEPLOY_PATH/.git" ]; then
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
echo "Starting pre-deploy backup..."
|
||||
cd "$DEPLOY_PATH"
|
||||
if [ -f Makefile ] && [ -f scripts/backup.sh ] && [ -f .env ]; then
|
||||
make predeploy-backup ENV_FILE=.env BACKUP_ROOT="$BACKUP_ROOT"
|
||||
else
|
||||
echo "Skipping pre-deploy backup because required backup files are missing in current deployment."
|
||||
fi
|
||||
else
|
||||
echo "Skipping pre-deploy backup because no existing deployment was found."
|
||||
fi
|
||||
REMOTE_BACKUP
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && git clean -fd"
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && mkdir -p uploads logs/nginx"
|
||||
|
||||
scp .env.deploy "${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_PATH}/.env"
|
||||
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && chmod 600 .env && docker compose -f docker-compose.prod.yaml config && docker compose -f docker-compose.prod.yaml up -d --build && docker compose -f docker-compose.prod.yaml restart nginx"
|
||||
|
||||
- name: Post-deploy status check
|
||||
env:
|
||||
PROD_HOST: ${{ vars.PROD_HOST }}
|
||||
PROD_USER: ${{ vars.PROD_USER }}
|
||||
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml ps"
|
||||
|
||||
- name: Post-deploy smoke checks
|
||||
env:
|
||||
PROD_HOST: ${{ vars.PROD_HOST }}
|
||||
PROD_USER: ${{ vars.PROD_USER }}
|
||||
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "curl -fsS http://localhost:9090/health"
|
||||
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml exec -T backend curl -fsS http://localhost:3000/health"
|
||||
|
||||
- name: Cleanup generated env file
|
||||
if: ${{ always() }}
|
||||
run: rm -f .env.deploy
|
||||
3
.gitignore
vendored
@@ -5,3 +5,6 @@ dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
backups/
|
||||
mysql_data/
|
||||
/docs/grafana_integration_proposal.md
|
||||
|
||||
12
Dockerfile.backend
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "server"]
|
||||
48
Dockerfile.backend.prod
Normal file
@@ -0,0 +1,48 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
LABEL maintainer="ITAM Team <devops@itam.local>"
|
||||
|
||||
# Set production environment
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install curl for health checks and dumb-init for proper signal handling
|
||||
RUN apk add --no-cache curl dumb-init mysql-client
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install production dependencies only
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy application code
|
||||
COPY server.js ./
|
||||
COPY src ./src
|
||||
|
||||
# Create non-root user 'appuser' with UID 1001 (1000 already in use by node image)
|
||||
RUN addgroup -g 1001 appuser && \
|
||||
adduser -D -u 1001 -G appuser appuser
|
||||
|
||||
# Set ownership of application files to appuser
|
||||
RUN chown -R appuser:appuser /app
|
||||
|
||||
# Create logs directory
|
||||
RUN mkdir -p /app/logs && \
|
||||
chown -R appuser:appuser /app/logs
|
||||
|
||||
# Switch to non-root user
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check - backend should implement /health endpoint
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/health || exit 1
|
||||
|
||||
# Use dumb-init from PATH to avoid distro-specific absolute path issues
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
# Run application
|
||||
CMD ["npm", "run", "server"]
|
||||
12
Dockerfile.frontend
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
62
Dockerfile.frontend.prod
Normal file
@@ -0,0 +1,62 @@
|
||||
# Stage 1: Build
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
COPY tsconfig*.json ./
|
||||
COPY vite.config.ts ./
|
||||
|
||||
# Install all dependencies (including devDependencies for build)
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY src ./src
|
||||
COPY public ./public
|
||||
COPY index.html map_editor.html mobile.html ./
|
||||
|
||||
# Build application
|
||||
RUN npm run build
|
||||
|
||||
# Verify build output
|
||||
RUN ls -la dist/ && echo "Build completed successfully"
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
LABEL maintainer="ITAM Team <devops@itam.local>"
|
||||
|
||||
# Install curl for health checks
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
WORKDIR /usr/share/nginx/html
|
||||
|
||||
# Copy built assets from builder
|
||||
COPY --from=builder /app/dist .
|
||||
|
||||
# Copy static image assets referenced by literal /img/... paths (Obsolete: img folder is now public/img and copied via dist)
|
||||
# COPY img ./img
|
||||
|
||||
# Copy root-level logo asset referenced directly by index.html
|
||||
# COPY ["image 92.png", "./image 92.png"]
|
||||
|
||||
# Copy Nginx static file serving configuration (not reverse proxy)
|
||||
COPY docker/frontend/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Create nginx runtime user and directories
|
||||
RUN mkdir -p /var/log/nginx && \
|
||||
chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /etc/nginx/conf.d
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
|
||||
CMD curl -f http://localhost:80/ || exit 1
|
||||
|
||||
# Run nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
33
Makefile
Normal file
@@ -0,0 +1,33 @@
|
||||
SHELL := /bin/sh
|
||||
|
||||
ENV_FILE ?= .env
|
||||
BACKUP_ROOT ?= backups
|
||||
RETENTION_DAYS ?= 14
|
||||
BACKUP_SCRIPT := scripts/backup.sh
|
||||
|
||||
.PHONY: help db-dump files-backup full-backup predeploy-backup cleanup-backups
|
||||
|
||||
help:
|
||||
@echo "Usage: make <target> [ENV_FILE=.env BACKUP_ROOT=backups RETENTION_DAYS=14]"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " db-dump Create a gzip-compressed MySQL dump from .env settings"
|
||||
@echo " files-backup Archive runtime files such as uploads/, map_config.json, and .env"
|
||||
@echo " full-backup Run both db-dump and files-backup"
|
||||
@echo " predeploy-backup Alias for the backup step executed before production deploy"
|
||||
@echo " cleanup-backups Delete backup files older than RETENTION_DAYS"
|
||||
|
||||
db-dump:
|
||||
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" db
|
||||
|
||||
files-backup:
|
||||
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" files
|
||||
|
||||
full-backup:
|
||||
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" full
|
||||
|
||||
predeploy-backup:
|
||||
@ENV_FILE="$(ENV_FILE)" BACKUP_ROOT="$(BACKUP_ROOT)" sh "$(BACKUP_SCRIPT)" full
|
||||
|
||||
cleanup-backups:
|
||||
@BACKUP_ROOT="$(BACKUP_ROOT)" RETENTION_DAYS="$(RETENTION_DAYS)" sh "$(BACKUP_SCRIPT)" cleanup
|
||||
@@ -1,429 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PC 사양 적정성 분석 기획서 (GPU 반영)</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary: #4F46E5;
|
||||
--primary-light: #EEF2FF;
|
||||
--secondary: #10B981;
|
||||
--secondary-light: #D1FAE5;
|
||||
--danger: #EF4444;
|
||||
--danger-light: #FEE2E2;
|
||||
--warning: #F59E0B;
|
||||
--warning-light: #FEF3C7;
|
||||
--purple: #7C3AED;
|
||||
--purple-light: #EDE9FE;
|
||||
--text-dark: #0F172A;
|
||||
--text-body: #334155;
|
||||
--text-muted: #64748B;
|
||||
--border: #E2E8F0;
|
||||
--bg-light: #F8FAFC;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: 'Outfit', 'Noto Sans KR', sans-serif;
|
||||
color: var(--text-body);
|
||||
background: #fff;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.page { max-width: 980px; margin: 0 auto; padding: 3rem 2rem; }
|
||||
|
||||
/* ─ Header ─ */
|
||||
.doc-header { border-bottom: 3px solid var(--text-dark); padding-bottom: 1.75rem; margin-bottom: 3rem; }
|
||||
.doc-label {
|
||||
display: inline-block; font-size: 0.75rem; font-weight: 700; color: var(--primary);
|
||||
background: var(--primary-light); padding: 0.25rem 0.75rem; border-radius: 99px;
|
||||
text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.75rem;
|
||||
}
|
||||
.version-badge {
|
||||
display: inline-block; font-size: 0.7rem; font-weight: 700; color: var(--secondary);
|
||||
background: var(--secondary-light); padding: 0.2rem 0.6rem; border-radius: 99px;
|
||||
margin-left: 0.5rem; vertical-align: middle;
|
||||
}
|
||||
.doc-header h1 { font-size: 2rem; font-weight: 900; color: var(--text-dark); line-height: 1.25; margin-bottom: 1rem; }
|
||||
.meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; margin-top: 1rem; }
|
||||
.meta-item { background: var(--bg-light); border-radius: 8px; padding: 0.65rem 1rem; font-size: 0.83rem; }
|
||||
.meta-item .label { color: var(--text-muted); display: block; font-size: 0.75rem; }
|
||||
.meta-item .val { font-weight: 700; color: var(--text-dark); font-size: 0.9rem; }
|
||||
|
||||
/* ─ Sections ─ */
|
||||
section { margin-bottom: 3.5rem; }
|
||||
h2 {
|
||||
font-size: 1.3rem; font-weight: 800; color: var(--text-dark);
|
||||
padding-bottom: 0.5rem; border-bottom: 2px solid var(--border);
|
||||
margin-bottom: 1.5rem; display: flex; align-items: center; gap: 0.6rem;
|
||||
}
|
||||
h2 .num {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 28px; height: 28px; background: var(--primary); color: #fff;
|
||||
border-radius: 50%; font-size: 0.75rem; font-weight: 800; flex-shrink: 0;
|
||||
}
|
||||
h3 { font-size: 1.05rem; font-weight: 700; color: var(--text-dark); margin: 1.75rem 0 0.75rem; }
|
||||
p { margin-bottom: 1rem; color: var(--text-body); font-size: 0.97rem; }
|
||||
|
||||
/* ─ Boxes ─ */
|
||||
.box { border-radius: 10px; padding: 1.25rem 1.5rem; margin: 1.25rem 0; font-size: 0.93rem; }
|
||||
.box-blue { background: var(--primary-light); border-left: 4px solid var(--primary); }
|
||||
.box-green { background: var(--secondary-light); border-left: 4px solid var(--secondary); }
|
||||
.box-yellow { background: var(--warning-light); border-left: 4px solid var(--warning); }
|
||||
.box-red { background: var(--danger-light); border-left: 4px solid var(--danger); }
|
||||
.box-purple { background: var(--purple-light); border-left: 4px solid var(--purple); }
|
||||
.box-title { font-weight: 700; color: var(--text-dark); margin-bottom: 0.5rem; font-size: 0.95rem; }
|
||||
|
||||
/* ─ Score formula block ─ */
|
||||
.formula {
|
||||
background: #1E293B; color: #E2E8F0; border-radius: 8px;
|
||||
padding: 1rem 1.25rem; font-family: 'Courier New', monospace;
|
||||
font-size: 0.87rem; margin: 1rem 0; overflow-x: auto; line-height: 2;
|
||||
}
|
||||
.formula .comment { color: #64748B; }
|
||||
.formula .key { color: #93C5FD; }
|
||||
.formula .val { color: #6EE7B7; }
|
||||
.formula .warn { color: #FCD34D; }
|
||||
|
||||
/* ─ Three-col score grid ─ */
|
||||
.score-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; margin: 1.5rem 0; }
|
||||
@media(max-width: 700px) { .score-grid-3 { grid-template-columns: 1fr; } }
|
||||
.score-card { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
|
||||
.score-card-header {
|
||||
background: var(--bg-light); padding: 0.65rem 1rem;
|
||||
font-weight: 700; font-size: 0.88rem; color: var(--text-dark);
|
||||
border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 0.5rem;
|
||||
}
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--primary); }
|
||||
.dot-green { background: var(--secondary); }
|
||||
.dot-purple { background: var(--purple); }
|
||||
|
||||
/* ─ Tables ─ */
|
||||
.tbl-wrap { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin: 1.25rem 0; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
|
||||
th { background: var(--bg-light); padding: 0.65rem 1rem; font-weight: 700; color: var(--text-dark); border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; }
|
||||
td { padding: 0.65rem 1rem; border-bottom: 1px solid var(--border); color: var(--text-body); vertical-align: top; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
tr:hover td { background: var(--bg-light); }
|
||||
|
||||
/* ─ Badges ─ */
|
||||
.badge { display: inline-block; padding: 0.2rem 0.55rem; border-radius: 4px; font-size: 0.75rem; font-weight: 700; white-space: nowrap; }
|
||||
.b-primary { color: var(--primary); background: var(--primary-light); }
|
||||
.b-green { color: #065F46; background: var(--secondary-light); }
|
||||
.b-red { color: #991B1B; background: var(--danger-light); }
|
||||
.b-yellow { color: #92400E; background: var(--warning-light); }
|
||||
.b-purple { color: #5B21B6; background: var(--purple-light); }
|
||||
|
||||
/* ─ Flow ─ */
|
||||
.flow { display: flex; align-items: center; flex-wrap: wrap; gap: 0; margin: 1.5rem 0; }
|
||||
.flow-step { background: var(--primary-light); color: var(--primary); font-weight: 700; font-size: 0.83rem; padding: 0.55rem 0.9rem; border-radius: 8px; text-align: center; }
|
||||
.flow-step.gpu { background: var(--purple-light); color: var(--purple); }
|
||||
.flow-arrow { font-size: 1.1rem; color: var(--text-muted); padding: 0 0.4rem; }
|
||||
|
||||
/* ─ GPU tier table highlight ─ */
|
||||
.tier-S td:first-child { font-weight: 800; color: #DC2626; }
|
||||
.tier-A td:first-child { font-weight: 700; color: var(--primary); }
|
||||
.tier-B td:first-child { font-weight: 700; color: var(--secondary); }
|
||||
.tier-C td:first-child { color: var(--warning); font-weight: 600; }
|
||||
.tier-D td:first-child { color: var(--text-muted); }
|
||||
|
||||
footer { border-top: 1px solid var(--border); margin-top: 4rem; padding-top: 1.5rem; text-align: center; font-size: 0.8rem; color: var(--text-muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- HEADER -->
|
||||
<header class="doc-header">
|
||||
<div class="doc-label">기능 명세서 <span class="version-badge">v3.0 — 100점 감점제 반영</span></div>
|
||||
<h1>PC 사양 적정성 분석 기획서<br>
|
||||
<span style="font-size:1.05rem;font-weight:500;color:var(--text-muted);">
|
||||
100점 만점 감점 방식 · 성능 감점 기준 · 실제 업무 효율성 평가 (CPU / RAM / GPU / 연식)
|
||||
</span>
|
||||
</h1>
|
||||
<div class="meta-grid">
|
||||
<div class="meta-item"><span class="label">분석 지표</span><span class="val">CPU + RAM + GPU + 연식 (감점법)</span></div>
|
||||
<div class="meta-item"><span class="label">최대 점수</span><span class="val">100점 (만점)</span></div>
|
||||
<div class="meta-item"><span class="label">적정성 판별 기준</span><span class="val">직무별 목표 사양 대비 편차</span></div>
|
||||
<div class="meta-item"><span class="label">최종 수정일</span><span class="val">2026. 05. 31</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 1. 개요 -->
|
||||
<section>
|
||||
<h2><span class="num">1</span>개요 — 100점 만점 감점형 성능 점수 체계</h2>
|
||||
<p>
|
||||
v3.0부터 PC 사양 점수는 <strong>100점 만점 기준 감점제</strong>로 산출됩니다.
|
||||
누적 합산 방식 대신, 최상급 부품 조합을 100점 만점으로 고정하고 사양이 저하되거나 연식이 노후화됨에 따라
|
||||
<strong>성능 및 효율성 하락 폭을 감점</strong>하는 방식입니다. 이는 실제 업무 환경에서 PC 노후도에 따른
|
||||
체감 생산성 저하를 훨씬 직관적이고 현실적으로 드러냅니다.
|
||||
</p>
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-step">① 기본 100점 만점</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">② CPU 등급/세대 감점</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">③ RAM 용량 감점</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step gpu">④ GPU 등급 감점</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">⑤ 연식 노후 감점</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">⑥ 최종 실질 성능 점수</div>
|
||||
</div>
|
||||
|
||||
<div class="formula">
|
||||
<span class="comment">// ─── 최종 PC 사양 점수 (100점 만점, 최소 10점 보존) ───</span>
|
||||
<span class="key">totalScore</span> = max(10, 100 - (<span class="val">cpuDeduction</span> + <span class="val">genDeduction</span> + <span class="val">ramDeduction</span> + <span class="val">gpuDeduction</span> + <span class="val">ageDeduction</span>))
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. CPU 감점 룰 -->
|
||||
<section>
|
||||
<h2><span class="num">2</span>CPU 사양 감점 기준</h2>
|
||||
<p>CPU 감점은 <strong>등급 감점(최대 -30점)</strong>과 <strong>세대 노후 감점(최대 -15점)</strong>의 합산입니다.</p>
|
||||
|
||||
<div class="formula">
|
||||
<span class="comment">// [CPU 등급 감점]</span>
|
||||
i9 / Ryzen 9 → <span class="val">0점 감점</span>
|
||||
i7 / Ryzen 7 → <span class="val">-5점 감점</span>
|
||||
i5 / Ryzen 5 → <span class="val">-15점 감점</span>
|
||||
i3 / Ryzen 3 → <span class="val">-25점 감점</span>
|
||||
기타 → <span class="val">-30점 감점</span>
|
||||
|
||||
<span class="comment">// [CPU 세대 노후 감점]</span>
|
||||
최신 세대 (Intel 12~14세대, Ryzen 5000~7000시리즈 이상) → <span class="val">0점 감점</span>
|
||||
과도기 세대 (Intel 10~11세대, Ryzen 3000시리즈) → <span class="val">-5점 감점</span>
|
||||
구형 세대 (Intel 8~9세대, Ryzen 1000~2000시리즈) → <span class="val">-10점 감점</span>
|
||||
노후 세대 (Intel 7세대 이하, 구형 AMD) → <span class="val">-15점 감점</span>
|
||||
</div>
|
||||
|
||||
<h3>CPU 조합별 감점 예시</h3>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>모델</th><th>세대 구분</th><th>등급감점</th><th>세대감점</th><th>CPU 감점 합계</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>i9-13900K</td><td>최신 세대</td><td>0</td><td>0</td><td><strong>0점 (감점 없음)</strong></td></tr>
|
||||
<tr><td>i7-14700K</td><td>최신 세대</td><td>-5</td><td>0</td><td><strong>-5점</strong></td></tr>
|
||||
<tr><td>i7-1360P</td><td>최신 세대 (노트북)</td><td>-5</td><td>0</td><td><strong>-5점</strong></td></tr>
|
||||
<tr><td>i5-12400</td><td>최신 세대</td><td>-15</td><td>0</td><td><strong>-15점</strong></td></tr>
|
||||
<tr><td>i7-9700</td><td>구형 세대</td><td>-5</td><td>-10</td><td><strong>-15점</strong></td></tr>
|
||||
<tr><td>i5-8500</td><td>구형 세대</td><td>-15</td><td>-10</td><td><strong>-25점</strong></td></tr>
|
||||
<tr><td>i7-7700</td><td>노후 세대</td><td>-5</td><td>-15</td><td><strong>-20점</strong></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. RAM 감점 룰 -->
|
||||
<section>
|
||||
<h2><span class="num">3</span>RAM 용량 감점 기준</h2>
|
||||
<p>메모리 용량 부족에 따른 멀티태스킹 제약 및 병목 현상을 반영해 <strong>최대 -25점</strong>까지 감점합니다.</p>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>RAM 용량</th><th>감점 점수</th><th>영향도</th><th>평가</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>32GB 이상</td><td><strong>0점 (감점 없음)</strong></td><td>대용량 3D 및 개발 작업 원활</td><td><span class="badge b-green">최적</span></td></tr>
|
||||
<tr><td>16GB</td><td><strong>-10점 감점</strong></td><td>일반 사무용 및 가벼운 멀티태스킹 적합</td><td><span class="badge b-primary">보통</span></td></tr>
|
||||
<tr><td>8GB</td><td><strong>-20점 감점</strong></td><td>브라우저 탭 다수 실행 시 물리 메모리 부족</td><td><span class="badge b-yellow">주의</span></td></tr>
|
||||
<tr><td>8GB 미만</td><td><strong>-25점 감점</strong></td><td>기본 OS 구동 외 심각한 메모리 병목</td><td><span class="badge b-red">부족</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. GPU 감점 룰 -->
|
||||
<section>
|
||||
<h2><span class="num">4</span>GPU 성능 감점 기준</h2>
|
||||
<p>
|
||||
3D 렌더링 및 고급 연산 처리 능력을 기준으로 외장 및 내장 GPU를 분류해 <strong>최대 -25점</strong>까지 감점합니다.
|
||||
GPU 정보가 감지되지 않거나 없는 경우 기본적으로 내장 그래픽 수준인 -25점을 감점합니다.
|
||||
</p>
|
||||
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>등급</th><th>제품군 구분</th><th>대표 모델</th><th>감점 점수</th><th>적합 작업</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="tier-S"><td>S</td><td>최상위 외장 GPU</td><td>RTX 4070~4090, RTX A4000~A6000</td><td><strong>0점 (감점 없음)</strong></td><td>3D 그래픽, AI 연산, VR</td></tr>
|
||||
<tr class="tier-A"><td>A</td><td>메인스트림 외장 GPU</td><td>RTX 3060~3070, RTX 2060, RTX A2000</td><td><strong>-5점 감점</strong></td><td>중급 개발, CAD 설계</td></tr>
|
||||
<tr class="tier-B"><td>B</td><td>엔트리 외장 GPU</td><td>GTX 1660, GTX 1060, RX 6600</td><td><strong>-15점 감점</strong></td><td>기본 CAD, 그래픽 보조</td></tr>
|
||||
<tr class="tier-C"><td>C</td><td>내장 그래픽 및 기타</td><td>Intel Iris Xe, UHD Graphics, Vega, GPU 없음</td><td><strong>-25점 감점</strong></td><td>오피스 사무, 문서 작업</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 종합 점수 감점 사례 -->
|
||||
<section>
|
||||
<h2><span class="num">5</span>감점법 종합 점수 계산 실사례</h2>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>모델명</th><th>CPU 사양 (감점)</th><th>RAM 사양 (감점)</th><th>GPU 사양 (감점)</th><th>연식 (감점)</th><th>감점 총합</th><th>최종 점수</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>HP ZBook Fury 16</td><td>Ryzen 9 7900X (0)</td><td>64GB (0)</td><td>NVIDIA RTX A2000 (-5)</td><td>2년차 (-6)</td><td>-11</td><td><strong>89점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dell Precision 5680</td><td>i9-13900K (0)</td><td>64GB (0)</td><td>NVIDIA RTX 4070 (0)</td><td>2년차 (-6)</td><td>-6</td><td><strong>94점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LG Gram 17 Pro</td><td>i7-14700K (-5)</td><td>32GB (0)</td><td>NVIDIA RTX 4060 (-5)</td><td>1년차 (-3)</td><td>-13</td><td><strong>87점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LG Gram 16</td><td>i7-1360P (-5)</td><td>16GB (-10)</td><td>Intel Iris Xe (-25)</td><td>3년차 (-9)</td><td>-49</td><td><strong>51점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Samsung Galaxy Book 3</td><td>i5-1340P (-15)</td><td>16GB (-10)</td><td>Intel Iris Xe (-25)</td><td>3년차 (-9)</td><td>-59</td><td><strong>41점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HP EliteBook 840</td><td>Ryzen 5 5600X (-15)</td><td>16GB (-10)</td><td>AMD Radeon Vega (-25)</td><td>4년차 (-12)</td><td>-62</td><td><strong>38점</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HP ProDesk 400 G5</td><td>i3-8100 (-35)</td><td>8GB (-20)</td><td>Intel UHD 630 (-25)</td><td>5년 이상 (-15)</td><td>-95</td><td><strong>10점(보존)</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. 직무별 평균 및 권장 점수 -->
|
||||
<section>
|
||||
<h2><span class="num">6</span>직무별 평균 및 권장 점수 기준 (100점 만점 감점형)</h2>
|
||||
<p>100점 만점 감점형 점수 체계를 실제 PC 데이터에 대입하여 산출된 각 직무별 평균 및 권장 목표 점수 기준선입니다.</p>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>정렬</th><th>직무</th><th>실제 데이터 평균 (감점 반영)</th><th>기본 권장 점수 (목표)</th><th>규칙</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td><strong>AI 개발자</strong></td><td>88.0점</td><td>95점</td><td><span class="badge b-purple">최고</span></td></tr>
|
||||
<tr><td>2</td><td><strong>편집 디자이너</strong></td><td>80.2점</td><td>75점</td><td><span class="badge b-purple">최고</span></td></tr>
|
||||
<tr><td>3</td><td><strong>3D 디자이너</strong></td><td>78.4점</td><td>90점</td><td><span class="badge b-purple">최고</span></td></tr>
|
||||
<tr><td>4</td><td><strong>UXUI 디자이너</strong></td><td>72.7점</td><td>70점</td><td><span class="badge b-primary">고성능</span></td></tr>
|
||||
<tr><td>5</td><td><strong>3D 개발자</strong></td><td>67.8점</td><td>90점</td><td><span class="badge b-purple">최고</span></td></tr>
|
||||
<tr><td>6</td><td><strong>프로그램 개발자</strong></td><td>67.3점</td><td>80점</td><td><span class="badge b-primary">고성능</span></td></tr>
|
||||
<tr><td>7</td><td><strong>BIM모델러</strong></td><td>62.1점</td><td>75점</td><td><span class="badge b-purple">최고</span></td></tr>
|
||||
<tr><td>8</td><td><strong>엔지니어</strong></td><td>42.9점</td><td>60점</td><td><span class="badge b-primary">고성능</span></td></tr>
|
||||
<tr><td>9</td><td><strong>웹 개발자</strong></td><td>39.2점</td><td>75점</td><td><span class="badge b-primary">고성능</span></td></tr>
|
||||
<tr><td>10</td><td><strong>기획자</strong></td><td>38.6점</td><td>50점</td><td><span class="badge b-green">중간</span></td></tr>
|
||||
<tr><td>11</td><td><strong>감리원</strong></td><td>-</td><td>40점</td><td><span class="badge b-yellow">기본</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box box-blue">
|
||||
<div class="box-title">📌 대소 관계 조건 충족 확인</div>
|
||||
AI 개발자(88.0) > 편집 디자이너(80.2) > 3D 디자이너(78.4) > UXUI 디자이너(72.7) > 3D 개발자(67.8) > 프로그램 개발자(67.3) > BIM모델러(62.1) > 엔지니어(42.9) > 웹 개발자(39.2) > 기획자(38.6) ✅
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. 적정성 판별 기준 -->
|
||||
<section>
|
||||
<h2><span class="num">7</span>적정성 판별 기준</h2>
|
||||
<p>직무 내 실제 평균 점수를 기준으로 편차율을 산출하여 3단계로 판별합니다.</p>
|
||||
<div class="formula">
|
||||
<span class="key">avgScore</span> = <span class="val">해당 직무 소속 PC 점수들의 산술 평균</span>
|
||||
|
||||
IF <span class="val">개인 실질 점수 < avgScore × 0.80</span> → <span class="key">"사양 부족"</span> (직무 평균 20% 이상 미달)
|
||||
IF <span class="val">개인 실질 점수 > avgScore × 1.30</span> → <span class="key">"오버스펙"</span> (직무 평균 30% 이상 초과)
|
||||
ELSE → <span class="key">"적정"</span>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>판별 결과</th><th>조건</th><th>권장 조치</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="badge b-red">사양 부족</span></td><td>실질 점수 < 직무 평균 × 0.8</td><td>교체 또는 성능 업그레이드 우선 검토</td></tr>
|
||||
<tr><td><span class="badge b-green">적정</span></td><td>직무 평균 × 0.8 ≤ 실질 점수 ≤ 직무 평균 × 1.3</td><td>현행 업무 효율 유지</td></tr>
|
||||
<tr><td><span class="badge b-yellow">오버스펙</span></td><td>실질 점수 > 직무 평균 × 1.3</td><td>과스펙 장비 회수 또는 필요 부서 재배치</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. 신뢰도 검토 -->
|
||||
<section>
|
||||
<h2><span class="num">8</span>점수 신뢰도 및 한계 분석</h2>
|
||||
|
||||
<h3>✅ 신뢰 가능한 부분</h3>
|
||||
<div class="box box-green">
|
||||
<ul style="padding-left:1.25rem;margin:0;line-height:2.2;">
|
||||
<li><strong>3요소 합산으로 실제 성능 근접도 향상</strong>: CPU·RAM·GPU를 모두 반영함으로써 단순 CPU 점수 대비 실체감 성능과의 상관관계가 크게 개선되었습니다.</li>
|
||||
<li><strong>GPU 티어 방향성 일치</strong>: RTX 4090 > 4080 > 4070 … 순의 점수 순서는 실제 벤치마크(3DMark, PassMark GPU)와 일치합니다.</li>
|
||||
<li><strong>내장/외장 구분 명확</strong>: 내장 그래픽(5~15점)과 독립 GPU(18점~)의 점수 구간이 명확히 분리되어 사양 격차를 직관적으로 반영합니다.</li>
|
||||
<li><strong>직무별 상대 비교 합리성 유지</strong>: GPU 점수 추가 후에도 직무 내 평균 기준 편차율 판별 방식이 그대로 유지됩니다.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>⚠️ 여전히 남아있는 한계점</h3>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>한계 항목</th><th>내용</th><th>영향도</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>노트북 TDP 미반영</strong></td>
|
||||
<td>i7-1360P (노트북 28W)와 i7-13700K (데스크탑 125W)는 같은 세대지만 실제 성능 차이가 큽니다. 현재는 동일 점수가 부여됩니다.</td>
|
||||
<td><span class="badge b-yellow">중간</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>SSD 유형 미반영</strong></td>
|
||||
<td>NVMe SSD와 HDD의 체감 속도 차이는 크지만 점수에 포함되지 않습니다.</td>
|
||||
<td><span class="badge b-yellow">중간</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>GPU 세부 파생 모델 한계</strong></td>
|
||||
<td>RTX 4060 Laptop과 RTX 4060 Desktop은 성능 차이가 있으나 동일 점수(50점)를 받습니다.</td>
|
||||
<td><span class="badge b-yellow">중간</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>GPU 세대 보정 미적용</strong></td>
|
||||
<td>CPU와 달리 GPU는 세대 보정 없이 모델명 매핑 방식만 사용됩니다. 향후 세대별 보정을 검토할 수 있습니다.</td>
|
||||
<td><span class="badge b-primary">낮음</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>실측 벤치마크 미연동</strong></td>
|
||||
<td>3DMark / PassMark GPU 실측값이 아닌 모델명 파싱 추정치입니다.</td>
|
||||
<td><span class="badge b-yellow">중간</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="box box-blue">
|
||||
<div class="box-title">💡 종합 신뢰도 평가</div>
|
||||
GPU 점수 반영 후 <strong>특히 디자이너·개발자와 같은 그래픽 집약적 직무의 적정성 판별 정확도가 대폭 향상</strong>되었습니다.
|
||||
다만 노트북 TDP, SSD 유형 등 추가 변수를 향후 보완하면 신뢰도를 더 끌어올릴 수 있습니다.
|
||||
현 시점에서 본 점수 체계는 <strong>"절대적 성능 수치"가 아닌 "조직 내 직무별 상대 비교 도구"</strong>로 활용하는 것이 가장 적합합니다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 9. 개선 로드맵 -->
|
||||
<section>
|
||||
<h2><span class="num">9</span>향후 개선 로드맵</h2>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr><th>우선순위</th><th>항목</th><th>기대 효과</th><th>난이도</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="badge b-green">완료</span></td><td>GPU 점수 반영 (v2.0)</td><td>그래픽 직무 신뢰도 대폭 향상</td><td>중</td></tr>
|
||||
<tr><td><span class="badge b-yellow">권장</span></td><td>SSD 유형별 점수 추가 (NVMe/SATA/HDD)</td><td>실체감 체감 속도 반영</td><td>하</td></tr>
|
||||
<tr><td><span class="badge b-yellow">권장</span></td><td>노트북/데스크탑 TDP 보정</td><td>모바일 CPU 과대평가 방지</td><td>중</td></tr>
|
||||
<tr><td><span class="badge b-primary">선택</span></td><td>PassMark / 3DMark 실측 DB 내장 연동</td><td>추정치 → 실측값 전환</td><td>상</td></tr>
|
||||
<tr><td><span class="badge b-primary">선택</span></td><td>직무별 항목 가중치 커스터마이징</td><td>조직 특성 맞춤 정밀 점수화</td><td>중</td></tr>
|
||||
<tr><td><span class="badge b-primary">선택</span></td><td>RMM 에이전트 실시간 자원 점유율 연동</td><td>실사용 기반 교체 우선순위 추천</td><td>상</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>HM ITAM — PC 사양 적정성 분석 기획서 v2.0 (GPU 반영) · 2026. 05. 28</p>
|
||||
<p style="margin-top:0.25rem;">내부 검토용 문서입니다. 무단 외부 배포를 금합니다.</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,10 @@
|
||||
- 기존 동작 방식과 성능을 기준(Baseline)으로 삼고, 수정 후에도 **기존의 모든 기능이 무결하게 유지되는지 반드시 테스트하여 입증**한다.
|
||||
- 검증 결과를 바탕으로 "무엇을, 왜, 어떻게" 바꿀지 상세 보고 후, 사용자로부터 **'진행시켜'** 승인을 얻은 뒤에만 집행한다.
|
||||
4. **선보고 후승인**: 모든 기능 수정 및 코드 변경 전에는 예상 방안을 먼저 보고하고 승인 절차를 거친다.
|
||||
5. **RED–GREEN–Refactor 개발 원칙**:
|
||||
5. **DB 삭제 및 초기화 절대 엄금 (Strict DB Deletion Policy)**:
|
||||
- 어떠한 경우에도 `DELETE`, `DROP`, `TRUNCATE` 등 데이터를 삭제하거나 테이블을 초기화하는 작업은 사전에 사용자에게 상세 사유를 보고하고 **명시적 승인**을 얻은 후에만 시행한다.
|
||||
- 기존 데이터의 가치를 최우선으로 하며, 작업 전 백업 여부를 반드시 확인한다.
|
||||
6. **RED–GREEN–Refactor 개발 원칙**:
|
||||
- 모든 기능 개발과 버그 수정은 **RED → GREEN → Refactor** 순서로 진행한다.
|
||||
- **RED**: 요구사항을 명확히 표현하는 테스트를 먼저 작성하고, 해당 테스트가 기능 미구현 또는 결함으로 인해 실패하는지 확인한다.
|
||||
- **GREEN**: 실패한 테스트를 통과시키는 데 필요한 최소한의 코드만 구현하며, 불필요한 기능 추가나 구조 변경을 하지 않는다.
|
||||
@@ -17,6 +20,9 @@
|
||||
- 각 단계가 끝날 때마다 관련 테스트와 기존 기능의 회귀 여부를 검증한다.
|
||||
- 테스트 작성이 현실적으로 불가능한 경우에는 그 사유와 대체 검증 방법을 먼저 보고하고 승인을 받은 후 진행한다.
|
||||
- 본 원칙을 적용할 때에도 기존의 **선보고 후승인** 및 **외과 수술식 수정** 규칙을 준수한다.
|
||||
7. **CSS 스타일 분리 원칙 (CSS Separation Policy)**:
|
||||
- HTML/JS/TS 파일 내에 인라인 `<style>` 태그를 절대 사용하지 않는다.
|
||||
- 모든 디자인 및 스타일 코드는 관련 `.css` 파일(예: `common.css`, `modal.css`, `dashboard.css` 등)에만 작성하여 구조와 디자인을 명확히 분리한다.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
('D:\\이태훈\\22전산자산조사\\ITAM\\dist\\pc_agent.exe',
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico',
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
|
||||
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
|
||||
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
|
||||
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
|
||||
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
|
||||
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
|
||||
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
|
||||
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
|
||||
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
|
||||
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
|
||||
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
|
||||
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
|
||||
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
|
||||
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
|
||||
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
|
||||
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
|
||||
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
|
||||
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
|
||||
True,
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\pc_agent.pkg',
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz', 'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\PYZ-00.pyz', 'PYZ'),
|
||||
('struct',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_cryptography_openssl',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_cryptography_openssl.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pywintypes',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_pywintypes.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pythoncom',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_pythoncom.py',
|
||||
'PYSOURCE'),
|
||||
('pc_agent', 'D:\\이태훈\\22전산자산조사\\ITAM\\pc_agent.py', 'PYSOURCE'),
|
||||
('python312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll',
|
||||
'BINARY'),
|
||||
('pywin32_system32\\pywintypes312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pywin32_system32\\pywintypes312.dll',
|
||||
'BINARY'),
|
||||
('pywin32_system32\\pythoncom312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pywin32_system32\\pythoncom312.dll',
|
||||
'BINARY'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_multiprocessing.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_multiprocessing.pyd',
|
||||
'EXTENSION'),
|
||||
('pyexpat.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\pyexpat.pyd',
|
||||
'EXTENSION'),
|
||||
('_ssl.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ssl.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('_queue.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_queue.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_wmi.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_overlapped.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_overlapped.pyd',
|
||||
'EXTENSION'),
|
||||
('_asyncio.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_asyncio.pyd',
|
||||
'EXTENSION'),
|
||||
('_cffi_backend.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_cffi_backend.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('cryptography\\hazmat\\bindings\\_rust.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography\\hazmat\\bindings\\_rust.pyd',
|
||||
'EXTENSION'),
|
||||
('charset_normalizer\\md__mypyc.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\charset_normalizer\\md__mypyc.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('charset_normalizer\\md.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\charset_normalizer\\md.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\_win32sysloader.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\_win32sysloader.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32api.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32api.pyd',
|
||||
'EXTENSION'),
|
||||
('Pythonwin\\win32ui.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\Pythonwin\\win32ui.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32event.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32event.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32trace.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32trace.pyd',
|
||||
'EXTENSION'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140_1.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140_1.dll',
|
||||
'BINARY'),
|
||||
('libssl-3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libssl-3.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libcrypto-3.dll',
|
||||
'BINARY'),
|
||||
('libffi-8.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libffi-8.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\python3.dll',
|
||||
'BINARY'),
|
||||
('Pythonwin\\mfc140u.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\Pythonwin\\mfc140u.dll',
|
||||
'BINARY'),
|
||||
('certifi\\cacert.pem',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\cacert.pem',
|
||||
'DATA'),
|
||||
('certifi\\py.typed',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\py.typed',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE.BSD',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE.BSD',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\RECORD',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\RECORD',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\METADATA',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\METADATA',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\WHEEL',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\WHEEL',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\INSTALLER',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\INSTALLER',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE.APACHE',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE.APACHE',
|
||||
'DATA'),
|
||||
('base_library.zip',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\base_library.zip',
|
||||
'DATA')],
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
1779102721,
|
||||
[('run.exe',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe',
|
||||
'EXECUTABLE')],
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll')
|
||||
@@ -1,189 +0,0 @@
|
||||
('D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\pc_agent.pkg',
|
||||
{'BINARY': True,
|
||||
'DATA': True,
|
||||
'EXECUTABLE': True,
|
||||
'EXTENSION': True,
|
||||
'PYMODULE': True,
|
||||
'PYSOURCE': True,
|
||||
'PYZ': False,
|
||||
'SPLASH': True,
|
||||
'SYMLINK': False},
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz', 'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\PYZ-00.pyz', 'PYZ'),
|
||||
('struct',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_cryptography_openssl',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_cryptography_openssl.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pywintypes',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_pywintypes.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pythoncom',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_pythoncom.py',
|
||||
'PYSOURCE'),
|
||||
('pc_agent', 'D:\\이태훈\\22전산자산조사\\ITAM\\pc_agent.py', 'PYSOURCE'),
|
||||
('python312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll',
|
||||
'BINARY'),
|
||||
('pywin32_system32\\pywintypes312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pywin32_system32\\pywintypes312.dll',
|
||||
'BINARY'),
|
||||
('pywin32_system32\\pythoncom312.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pywin32_system32\\pythoncom312.dll',
|
||||
'BINARY'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_multiprocessing.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_multiprocessing.pyd',
|
||||
'EXTENSION'),
|
||||
('pyexpat.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\pyexpat.pyd',
|
||||
'EXTENSION'),
|
||||
('_ssl.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ssl.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('_queue.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_queue.pyd',
|
||||
'EXTENSION'),
|
||||
('_wmi.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_wmi.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_overlapped.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_overlapped.pyd',
|
||||
'EXTENSION'),
|
||||
('_asyncio.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_asyncio.pyd',
|
||||
'EXTENSION'),
|
||||
('_cffi_backend.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\_cffi_backend.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('cryptography\\hazmat\\bindings\\_rust.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography\\hazmat\\bindings\\_rust.pyd',
|
||||
'EXTENSION'),
|
||||
('charset_normalizer\\md__mypyc.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\charset_normalizer\\md__mypyc.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('charset_normalizer\\md.cp312-win_amd64.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\charset_normalizer\\md.cp312-win_amd64.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\_win32sysloader.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\_win32sysloader.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32api.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32api.pyd',
|
||||
'EXTENSION'),
|
||||
('Pythonwin\\win32ui.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\Pythonwin\\win32ui.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32event.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32event.pyd',
|
||||
'EXTENSION'),
|
||||
('win32\\win32trace.pyd',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\win32\\win32trace.pyd',
|
||||
'EXTENSION'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140_1.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140_1.dll',
|
||||
'BINARY'),
|
||||
('libssl-3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libssl-3.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libcrypto-3.dll',
|
||||
'BINARY'),
|
||||
('libffi-8.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libffi-8.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\python3.dll',
|
||||
'BINARY'),
|
||||
('Pythonwin\\mfc140u.dll',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\Pythonwin\\mfc140u.dll',
|
||||
'BINARY'),
|
||||
('certifi\\cacert.pem',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\cacert.pem',
|
||||
'DATA'),
|
||||
('certifi\\py.typed',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\py.typed',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE.BSD',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE.BSD',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\RECORD',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\RECORD',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\METADATA',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\METADATA',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\WHEEL',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\WHEEL',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\INSTALLER',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\INSTALLER',
|
||||
'DATA'),
|
||||
('cryptography-45.0.2.dist-info\\licenses\\LICENSE.APACHE',
|
||||
'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\cryptography-45.0.2.dist-info\\licenses\\LICENSE.APACHE',
|
||||
'DATA'),
|
||||
('base_library.zip',
|
||||
'D:\\이태훈\\22전산자산조사\\ITAM\\build\\pc_agent\\base_library.zip',
|
||||
'DATA')],
|
||||
'python312.dll',
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean these modules are required for running your program. Both
|
||||
Python's standard library and 3rd-party Python packages often conditionally
|
||||
import optional modules, some of which may be available only on certain
|
||||
platforms.
|
||||
|
||||
Types of import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), netrc (delayed, conditional), getpass (delayed)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||
missing module named fcntl - imported by subprocess (optional)
|
||||
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||
missing module named _scproxy - imported by urllib.request (conditional)
|
||||
missing module named termios - imported by getpass (optional)
|
||||
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named pyimod02_importers - imported by C:\Users\User\AppData\Local\Programs\Python\Python312\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
||||
missing module named collections.Callable - imported by collections (optional), socks (optional)
|
||||
missing module named vms_lib - imported by platform (delayed, optional)
|
||||
missing module named 'java.lang' - imported by platform (delayed, optional)
|
||||
missing module named java - imported by platform (delayed)
|
||||
missing module named _winreg - imported by platform (delayed, optional)
|
||||
missing module named simplejson - imported by requests.compat (conditional, optional)
|
||||
missing module named dummy_threading - imported by requests.cookies (optional)
|
||||
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||
missing module named annotationlib - imported by typing_extensions (conditional)
|
||||
missing module named 'h2.events' - imported by urllib3.http2.connection (top-level)
|
||||
missing module named 'h2.connection' - imported by urllib3.http2.connection (top-level)
|
||||
missing module named h2 - imported by urllib3.http2.connection (top-level)
|
||||
missing module named zstandard - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named win_inet_pton - imported by socks (conditional, optional)
|
||||
missing module named bcrypt - imported by cryptography.hazmat.primitives.serialization.ssh (optional)
|
||||
missing module named cryptography.x509.UnsupportedExtension - imported by cryptography.x509 (optional), urllib3.contrib.pyopenssl (optional)
|
||||
missing module named 'OpenSSL.crypto' - imported by urllib3.contrib.pyopenssl (delayed, conditional)
|
||||
missing module named OpenSSL - imported by urllib3.contrib.pyopenssl (top-level)
|
||||
missing module named 'pyodide.ffi' - imported by urllib3.contrib.emscripten.fetch (delayed, optional)
|
||||
missing module named pyodide - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
missing module named js - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
missing module named 'win32com.gen_py' - imported by win32com (conditional, optional)
|
||||
73
docker-compose.prod.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
services:
|
||||
backend:
|
||||
image: itam-backend:prod
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend.prod
|
||||
container_name: itam-backend
|
||||
working_dir: /app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./map_config.json:/app/map_config.json
|
||||
expose:
|
||||
- "3000"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
frontend:
|
||||
image: itam-frontend:prod
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.frontend.prod
|
||||
container_name: itam-frontend
|
||||
expose:
|
||||
- "80"
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:stable-alpine
|
||||
container_name: itam-nginx
|
||||
ports:
|
||||
- "9090:80"
|
||||
volumes:
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./logs/nginx:/var/log/nginx
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
frontend:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
database:
|
||||
image: mysql:latest
|
||||
container_name: itam-mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=itam1234 # 여기 직접 기입
|
||||
- MYSQL_DATABASE=itam
|
||||
- MYSQL_USER=itam
|
||||
- MYSQL_PASSWORD=itam1234
|
||||
volumes:
|
||||
- ./mysql_data:/var/lib/mysql
|
||||
restart: always
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
62
docker-compose.test.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
# Local testing compose file - uses relative paths and build contexts
|
||||
# Usage: docker compose -f docker-compose.test.yaml up --build
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend.prod
|
||||
container_name: itam-backend-test
|
||||
working_dir: /app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
PORT: 3000
|
||||
DB_HOST: ${DB_HOST:-172.16.8.151}
|
||||
DB_PORT: ${DB_PORT:-3306}
|
||||
DB_USER: ${DB_USER:-root}
|
||||
DB_PASS: ${DB_PASS:-}
|
||||
DB_NAME: ${DB_NAME:-itam}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./map_config.json:/app/map_config.json
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.frontend.prod
|
||||
container_name: itam-frontend-test
|
||||
expose:
|
||||
- "80"
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:stable-alpine
|
||||
container_name: itam-nginx-test
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./logs/nginx:/var/log/nginx
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
frontend:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
48
docker-compose.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend
|
||||
container_name: dachs-backend
|
||||
working_dir: /app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASS: ${DB_PASS}
|
||||
DB_NAME: ${DB_NAME}
|
||||
PORT: 3000
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./:/app
|
||||
- backend_node_modules:/app/node_modules
|
||||
- ./uploads:/app/uploads
|
||||
- ./map_config.json:/app/map_config.json
|
||||
command: npm run server
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.frontend
|
||||
container_name: dachs-frontend
|
||||
working_dir: /app
|
||||
depends_on:
|
||||
- backend
|
||||
environment:
|
||||
CHOKIDAR_USEPOLLING: "true"
|
||||
VITE_DEV_PROXY_TARGET: http://backend:3000
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./:/app
|
||||
- frontend_node_modules:/app/node_modules
|
||||
command: npm run dev -- --host 0.0.0.0
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
backend_node_modules:
|
||||
frontend_node_modules:
|
||||
56
docker/frontend/default.conf
Normal file
@@ -0,0 +1,56 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/frontend-access.log main;
|
||||
error_log /var/log/nginx/frontend-error.log warn;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_types text/plain text/css text/xml text/javascript
|
||||
application/x-javascript application/xml+rss
|
||||
application/json application/javascript;
|
||||
gzip_min_length 1000;
|
||||
|
||||
# Serve static files with SPA fallback
|
||||
location / {
|
||||
rewrite ^/mobile$ /mobile.html last;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets (60 days)
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
|
||||
expires 60d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Don't cache HTML files
|
||||
location ~* \.html$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# Deny access to sensitive files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
16
docker/mysql/init/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# MySQL init directory
|
||||
|
||||
This directory is kept as a legacy hook for file-based MySQL initialization.
|
||||
|
||||
Current production path in this repository is not file-based import.
|
||||
The live Docker flow uses the `db-bootstrap` service in `docker-compose.yaml` to stream data from the external source DB into the internal `db` container.
|
||||
|
||||
Use this directory only if you intentionally switch back to `docker-entrypoint-initdb.d` style initialization.
|
||||
|
||||
If you do that, typical naming would be:
|
||||
|
||||
- `01_schema.sql`
|
||||
- `02_seed.sql`
|
||||
- or a single `01_itam_dump.sql`
|
||||
|
||||
Remember that files in this directory are executed automatically by the MySQL container only on the first initialization of the data volume.
|
||||
101
docker/nginx/default.conf
Normal file
@@ -0,0 +1,101 @@
|
||||
upstream backend {
|
||||
server backend:3000;
|
||||
}
|
||||
|
||||
upstream frontend {
|
||||
server frontend:80;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
# Client upload size limit (adjust as needed)
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_types text/plain text/css text/xml text/javascript
|
||||
application/x-javascript application/xml+rss
|
||||
application/json application/javascript;
|
||||
gzip_min_length 1000;
|
||||
|
||||
# Forward all app requests to the frontend container
|
||||
location / {
|
||||
proxy_pass http://frontend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
# API proxy to backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend/api/;
|
||||
|
||||
# Preserve original request information
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
|
||||
# Connection settings
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# Buffering settings
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
}
|
||||
|
||||
# Uploads proxy to backend
|
||||
location /uploads/ {
|
||||
proxy_pass http://backend/uploads/;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Cache uploads
|
||||
expires 30d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Health check endpoint (for monitoring)
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# Deny access to sensitive files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location ~ ~$ {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
226
docs/itam_cicd_setup.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# ITAM CI/CD 설정 가이드
|
||||
|
||||
## 1. 문서 목적
|
||||
|
||||
이 문서는 현재 ITAM 저장소에 구성된 CI/CD 파일을 실제 운영에 연결하기 위해 필요한 기준을 정리한 가이드다.
|
||||
|
||||
대상 범위는 아래와 같다.
|
||||
|
||||
1. Gitea Actions workflow 역할
|
||||
2. Gitea Variables / Secrets 설정값
|
||||
3. 운영 서버 배포 디렉토리 기준
|
||||
4. 운영 영속 경로 기준
|
||||
5. production deploy 실행 전 확인 사항
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 CI/CD 구성
|
||||
|
||||
현재 `.gitea/workflows`에는 ITAM 관련 workflow만 남겨둔 상태다.
|
||||
|
||||
1. `itam_code_check.yml`
|
||||
2. `itam_docker_build_check.yml`
|
||||
3. `itam_production_deploy.yml`
|
||||
|
||||
각 workflow의 역할은 아래와 같다.
|
||||
|
||||
1. `itam_code_check.yml`: TypeScript/Vite build와 compose 문법 검증
|
||||
2. `itam_docker_build_check.yml`: 운영용 Docker 이미지 빌드 가능 여부 검증
|
||||
3. `itam_production_deploy.yml`: 운영 서버에 SSH 접속 후 실제 배포 수행
|
||||
|
||||
현재 배포 흐름은 아래와 같다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
DEV["Developer Push or Manual Run"] --> CODE["ITAM Code Check"]
|
||||
CODE --> BUILD["ITAM Docker Build Check"]
|
||||
BUILD --> DEPLOY["ITAM Production Deploy"]
|
||||
DEPLOY --> HOST["Production Host"]
|
||||
HOST --> APP["docker compose up -d --build"]
|
||||
linkStyle default stroke:#d32f2f,stroke-width:2px;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Gitea Variables / Secrets 기준
|
||||
|
||||
`itam_production_deploy.yml`이 정상 동작하려면 아래 값이 필요하다.
|
||||
|
||||
### 3.1 Variables
|
||||
|
||||
아래 항목은 Gitea repository Variables에 등록한다.
|
||||
|
||||
| Key | 설명 | 예시 |
|
||||
| --- | --- | --- |
|
||||
| `PROD_HOST` | 운영 서버 SSH 접속 호스트 | `10.0.0.25` |
|
||||
| `PROD_USER` | 운영 서버 SSH 사용자 | `deploy` |
|
||||
| `PROD_DEPLOY_PATH` | 서버에서 저장소를 배포할 경로 | `/opt/itam` |
|
||||
| `PROD_BACKUP_ROOT` | 배포 전 백업 저장 경로, 배포 경로 바깥이어야 함 | `/opt/itam-backups` |
|
||||
| `PROD_GIT_URL` | 운영 서버에서 pull 가능한 저장소 주소 | `git@gitea.example.com:team/itam.git` |
|
||||
| `PROD_DB_HOST` | 외부 MySQL 호스트 | `172.16.8.151` |
|
||||
| `PROD_DB_PORT` | 외부 MySQL 포트 | `3306` |
|
||||
| `PROD_DB_USER` | 운영 DB 계정 | `itam_admin` |
|
||||
| `PROD_DB_NAME` | 운영 DB 이름 | `itam` |
|
||||
| `PROD_LOG_LEVEL` | 애플리케이션 로그 레벨 | `info` |
|
||||
|
||||
### 3.2 Secrets
|
||||
|
||||
아래 항목은 Gitea repository Secrets에 등록한다.
|
||||
|
||||
| Key | 설명 |
|
||||
| --- | --- |
|
||||
| `PROD_SSH_PRIVATE_KEY` | 운영 서버 접속용 개인키 |
|
||||
| `PROD_DB_PASS` | 운영 DB 비밀번호 |
|
||||
|
||||
### 3.3 운영 원칙
|
||||
|
||||
1. `PROD_DB_PASS`는 Variables가 아니라 Secrets에만 둔다.
|
||||
2. `PROD_SSH_PRIVATE_KEY`는 배포 전용 계정 키를 사용한다.
|
||||
3. `PROD_GIT_URL`은 운영 서버에서 직접 pull 가능한 주소여야 한다.
|
||||
4. 운영 서버의 `known_hosts`는 workflow에서 자동 등록되지만, 최초 운영 전 수동 접속 검증도 함께 수행하는 것이 안전하다.
|
||||
5. `PROD_BACKUP_ROOT`는 `PROD_DEPLOY_PATH` 내부가 아니라 바깥 경로를 사용해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 운영 서버 배포 디렉토리 기준
|
||||
|
||||
현재 `itam_production_deploy.yml`은 운영 서버에서 아래 흐름으로 배포를 수행한다.
|
||||
|
||||
1. `PROD_DEPLOY_PATH` 디렉토리를 생성한다.
|
||||
2. 기존 운영 상태가 있으면 배포 전 백업을 수행한다.
|
||||
3. 해당 경로에 저장소를 clone 또는 fetch 한다.
|
||||
4. 지정 브랜치로 checkout 한다.
|
||||
5. `uploads`, `logs/nginx` 디렉토리를 생성한다.
|
||||
6. `.env.deploy`를 서버의 `.env`로 복사한다.
|
||||
7. `docker compose -f docker-compose.prod.yaml up -d --build`를 실행한다.
|
||||
|
||||
권장 디렉토리 구조는 아래와 같다.
|
||||
|
||||
```text
|
||||
/opt/itam/
|
||||
.env
|
||||
docker-compose.prod.yaml
|
||||
Dockerfile.frontend.prod
|
||||
Dockerfile.backend.prod
|
||||
map_config.json
|
||||
uploads/
|
||||
logs/
|
||||
nginx/
|
||||
docker/
|
||||
nginx/
|
||||
default.conf
|
||||
frontend/
|
||||
default.conf
|
||||
src/
|
||||
public/
|
||||
img/
|
||||
|
||||
/opt/itam-backups/
|
||||
db/
|
||||
files/
|
||||
```
|
||||
|
||||
현재 구조 기준 배포 관계는 아래와 같다.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph HOST["Production Host"]
|
||||
REPO["PROD_DEPLOY_PATH"]
|
||||
ENV[".env"]
|
||||
UP["uploads/"]
|
||||
LOGS["logs/nginx/"]
|
||||
MAP["map_config.json"]
|
||||
end
|
||||
|
||||
subgraph CTR["Docker Services"]
|
||||
NGINX["itam-nginx"]
|
||||
FRONT["itam-frontend"]
|
||||
BACK["itam-backend"]
|
||||
end
|
||||
|
||||
DB["External MySQL"]
|
||||
|
||||
REPO --> NGINX
|
||||
ENV --> BACK
|
||||
UP --> BACK
|
||||
MAP --> BACK
|
||||
LOGS --> NGINX
|
||||
REPO --> BAK["PROD_BACKUP_ROOT"]
|
||||
NGINX --> FRONT
|
||||
NGINX --> BACK
|
||||
BACK --> DB
|
||||
linkStyle default stroke:#d32f2f,stroke-width:2px;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 영속 경로 기준
|
||||
|
||||
현재 `docker-compose.prod.yaml` 기준으로 운영에서 유지되어야 하는 경로는 아래와 같다.
|
||||
|
||||
1. `.env`
|
||||
2. `uploads/`
|
||||
3. `map_config.json`
|
||||
4. `logs/nginx/`
|
||||
5. `PROD_BACKUP_ROOT`
|
||||
|
||||
각 경로의 의미는 아래와 같다.
|
||||
|
||||
1. `.env`: backend 런타임 환경변수
|
||||
2. `uploads/`: 업로드 파일 데이터
|
||||
3. `map_config.json`: 위치/맵 구성 데이터
|
||||
4. `logs/nginx/`: reverse proxy 접근 로그 및 에러 로그
|
||||
5. `PROD_BACKUP_ROOT`: 배포 전 DB dump와 운영 파일 아카이브 저장 위치
|
||||
|
||||
운영 기준으로 보면 `uploads/`와 `map_config.json`은 애플리케이션 데이터이고, `.env`는 환경 설정이며, `logs/nginx/`는 운영 추적 데이터다.
|
||||
|
||||
즉 서버 운영 시 컨테이너만 다시 띄우면 되는 구조가 아니라, 이 경로들이 유지되는 것을 전제로 배포가 성립한다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 배포 전 체크리스트
|
||||
|
||||
`itam_production_deploy.yml` 실행 전 아래 항목을 먼저 확인한다.
|
||||
|
||||
1. 운영 서버에 Docker Engine과 `docker compose`가 설치되어 있어야 한다.
|
||||
2. 운영 서버의 배포 계정이 Docker 실행 권한을 가져야 한다.
|
||||
3. 운영 서버에서 `PROD_GIT_URL`로 직접 `git fetch`가 가능해야 한다.
|
||||
4. 외부 MySQL 접속 정보가 실제 운영망 기준으로 열려 있어야 한다.
|
||||
5. 운영 서버에 `map_config.json` 초기 파일이 존재해야 한다.
|
||||
6. 방화벽 또는 보안 장비에서 80 포트 접근 정책이 정리되어 있어야 한다.
|
||||
|
||||
권장 확인 명령 예시는 아래와 같다.
|
||||
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
git ls-remote <PROD_GIT_URL>
|
||||
test -f map_config.json
|
||||
test -d uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 현재 구조에서의 해석
|
||||
|
||||
현재 ITAM CI/CD는 staging 없이도 운영 배포가 가능한 최소 구조로 정리되어 있다.
|
||||
|
||||
이 구조의 장점은 아래와 같다.
|
||||
|
||||
1. workflow 수가 적어서 관리가 단순하다.
|
||||
2. 운영 배포에 필요한 변수와 시크릿 범위가 명확하다.
|
||||
3. staging이 필요해지면 production deploy workflow를 복제해 별도 환경으로 확장하기 쉽다.
|
||||
|
||||
즉, 지금 단계에서는 production 기준을 먼저 고정하고, staging은 동일 패턴으로 추후 추가하는 전략이 적절하다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 다음 권장 작업
|
||||
|
||||
현재 문서 기준으로 바로 이어서 할 작업은 아래 순서가 적절하다.
|
||||
|
||||
1. `PROD_DEPLOY_PATH` 실제 서버 경로 확정
|
||||
2. 운영 서버 배포 계정 생성 및 SSH 키 등록
|
||||
3. `map_config.json`, `uploads/` 초기 데이터 준비
|
||||
4. production deploy workflow에 smoke check 추가
|
||||
5. 로그 로테이션과 백업/복구 절차 문서화
|
||||
@@ -1,51 +0,0 @@
|
||||
# 구조 개선 및 다중 탭(Depth 2) 도입 계획
|
||||
|
||||
사용자 요청에 따라 H/W와 S/W를 구분하고, 그 하위에 각각 대시보드 및 상세 항목(개인PC, 서버 등) 탭을 나누는 네비게이션 구조를 도입합니다. 바닐라 JS 기반에서 각 탭마다 다른 데이터 테이블을 그려내는 아키텍처로 개선합니다.
|
||||
|
||||
## User Review Required
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 1. **엑셀 관리 방식 (Sheets 분리)**: 단일 엑셀 파일 안에 여러 개의 시트(Sheet)를 나누어 관리하는 방식으로 제안합니다. 한 번 엑셀을 업로드하면, `개인PC`, `서버`, `스토리지`, `전산비품` 등 각각의 시트를 한방에 파싱하여 각 탭에 적용하도록 구성하겠습니다.
|
||||
> 2. **S/W 스키마**: 현재 H/W 기반 데이터 스키마만 정의되어 있습니다. [구독 소프트웨어]와 [영구 소프트웨어] 탭 개발을 위한 데이터 항목들(예: 사용기간, 라이선스키, 결제방식 등)은 아직 정해지지 않았으므로 일단 공통 S/W 데이터 스키마 임시 템플릿(S/W명, 유형, 라이선스키, 할당된 사용자 등)으로 만들어 두고 추후 수정할 수 있도록 개발해도 될까요?
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. UI/UX: 2 Depth 네비게이션 (`index.html`, `style.css`)
|
||||
- **좌측(또는 상단) GNB (Global Navigation Bar)**: H/W 와 S/W 를 스위치할 수 있는 메인 탭 생성.
|
||||
- **LNB (Local Navigation Bar)**: 메인 탭 전환 시 나타나는 서브 탭(H/W: 대시보드/PC/서버/스토리지/비품, S/W: 대시보드/구독/영구).
|
||||
- `README.md` 가이드라인에 따라 화면을 분할하고 정보 밀도를 높이기 위해 Box-less, Line-based Layout 유지.
|
||||
|
||||
#### [MODIFY] index.html
|
||||
#### [MODIFY] src/style.css
|
||||
|
||||
---
|
||||
|
||||
### 2. 다중 데이터 구조 및 상태 관리 (`main.ts`)
|
||||
- 현재 선택된 메뉴 뎁스(예: `activeCategory = 'HW'`, `activeSubTab = '개인PC'`)에 따라 렌더링 함수가 동기화되도록 라우팅/상태 관리 로직 추가.
|
||||
- `Dashboard` 탭 진입 시, 모든 서브 탭 데이터의 갯수(Total PCs, Total Servers 등)를 한눈에 볼 수 있는 요약 영역(Summary Cards/Charts 영역) 예약 및 구현.
|
||||
|
||||
#### [MODIFY] src/main.ts
|
||||
|
||||
---
|
||||
|
||||
### 3. 멀티-시트(Multi-sheet) 엑셀 파싱 (`excelHandler.ts`)
|
||||
- `SheetJS` 기능을 확장하여 다운로드/데이터 추출 시 다중 시트 생성.
|
||||
- **H/W 템플릿 시트명**: `[개인PC, 서버, 스토리지, 전산비품]`
|
||||
- **S/W 템플릿 시트명**: `[구독SW, 영구SW]`
|
||||
|
||||
#### [MODIFY] src/excelHandler.ts
|
||||
|
||||
## Open Questions
|
||||
|
||||
> [!WARNING]
|
||||
> * 왼쪽 사이드바로 메뉴를 구성하는 것이 좋을까요, 상단 가로바(Top Nav) 2단으로 구성하는 것이 좋을까요? Reference 이미지가 따로 없다면 범용적으로 관리하기 편한 **왼쪽 사이드바 구조(Sidebar Menu)** 를 제안합니다. (진행 승인 시 사이드바 형태로 구현합니다.)
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Automated Tests
|
||||
- 좌측 `H/W`, `S/W` 클릭 시 서브 메뉴가 정상 토글되는지 검증(`main.ts` DOM class toggle 확인).
|
||||
- 서브 메뉴 `서버` 클릭 시 빈 테이블(또는 서버 자산 테이블)이 그려지는지 확인.
|
||||
- 달라진 구조로 `엑셀 템플릿 양식`을 다운로드했을 때 파일에 다수의 시트(Sheet)가 정상 분류되어 있는지 확인.
|
||||
|
||||
### Manual Verification
|
||||
- 브라우저 에이전트를 통해 바뀐 화면의 스크린샷(LNB 사이드바, Dashboard 화면 등)을 찍어 사용자에게 보고.
|
||||
@@ -1,47 +0,0 @@
|
||||
# 임시 DB 생성 및 S/W 사용자 관리 개편
|
||||
|
||||
임시 DB 엑셀 파일 생성과 S/W 목록의 '할당자' 속성 UI 개편에 대한 기술 구현 계획입니다.
|
||||
|
||||
## User Review Required
|
||||
> [!IMPORTANT]
|
||||
> **사용자 관리 데이터 저장 방식에 대한 피드백이 필요합니다.**
|
||||
> 엑셀을 임시 DB로 사용하고 있기 때문에, "사용자 관리" 팝업에서 추가/삭제된 사용자 목록을 엑셀에 저장할 때 **쉼표(,)로 구분된 하나의 문자열**(예: `홍길동, 김철수, 이영희`)로 기존 `할당자` 컬럼에 업데이트 하는 방식을 제안합니다. 이 방식이 괜찮으신가요?
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. 임시 DB 연동
|
||||
임시로 사용할 초기 엑셀 파일(`temp_db.xlsx`)을 프로젝트 루트에 스크립트를 통해 생성합니다.
|
||||
- 개인PC, 서버, 구독SW, 영구SW 시트에 각각 구성을 확인할 수 있는 dummy 데이터 1~2개씩을 포함하여 생성합니다.
|
||||
- 향후 화면에서 '엑셀 업로드'를 통해 이 파일을 업로드하여 데이터를 화면에 뿌려볼 수 있습니다. (원하시면 페이지 로드 시 이 파일을 임포트하도록 로직을 변경할 수도 있으나, 브라우저 단에서 로컬 파일을 자동 리딩하는 것은 제한이 있으므로 기본적으로는 파일을 제공만 합니다.)
|
||||
|
||||
---
|
||||
|
||||
### 2. 컴포넌트: HTML 구조 변경
|
||||
#### [MODIFY] [index.html](file:///c:/Project/HM%20ITAM/index.html)
|
||||
- `sw-asset-modal`의 폼 내용 중 "할당자" 입력 폼(<label> 및 <input>) 제거
|
||||
- 관리 팝업을 위한 `sw-user-modal` 모달 오버레이 마크업 추가
|
||||
기존 유저 목록을 보여주고, 새 사용자를 추가하거나 기존 사용자를 삭제할 수 있는 UI (리스트, 추가 인풋, 추가 버튼 기반) 작성
|
||||
|
||||
---
|
||||
|
||||
### 3. 컴포넌트: 로직 및 스타일
|
||||
#### [MODIFY] [src/main.ts](file:///c:/Project/HM%20ITAM/src/main.ts)
|
||||
- S/W 렌더링 영역(`renderTable`)에서 데스크탑 뷰의 `<th>할당자</th>` 및 해당하는 셀(`<td>`) 제거
|
||||
- S/W `관리` 탭(`<td>`)에 수정 버튼(`btn-edit`) 옆에 사용자 관리 아이콘 (Lucide의 `Users` 또는 `UserCog` 아이콘 활용) 추가
|
||||
- 사용자 관리 아이콘 클릭 시 `sw-user-modal` 팝업 띄우는 이벤트 리스너 추가
|
||||
- `sw-user-modal` 팝업 내에서 사용자를 추가/삭제하고 '저장' 시, 해당 S/W 자산의 `할당자` 데이터를 갱신하도록 처리 (쉼표 구분 형태)
|
||||
|
||||
#### [MODIFY] [src/excelHandler.ts](file:///c:/Project/HM%20ITAM/src/excelHandler.ts)
|
||||
- (선택 사항) `SW_HEADERS`나 엑셀 파싱 로직은 그대로 두어 하위 호환성 유지. 사용자가 데이터를 쉼표 형태로 주고 받을 것이므로 별도의 인터페이스 변경은 없음.
|
||||
|
||||
## Open Questions
|
||||
- 사용자 관리 팝업에서 저장할 때, 이름 말고 '부서'나 '직급' 같은 추가적인 정보도 관리가 필요하신가요? (기본적으로는 엑셀에 단일 텍스트로 보존되므로 '이름'만 관리하는 것으로 설계했습니다.)
|
||||
- 개발 환경(Vite)에서 초기 로딩 시 `temp_db.xlsx`를 자동으로 불러오도록 Vite의 플러그인 또는 fetch 로직을 추가하는 것을 원하시나요? 아니면 엑셀 파일만 만들어 드리고 사용자가 '엑셀 업로드' 버튼으로 직접 연동해 쓰는 방식이 좋으신가요?
|
||||
|
||||
## Verification Plan
|
||||
### Manual Verification
|
||||
1. `npm run dev` 후 브라우저 접속
|
||||
2. 프로젝트 폴더에 `temp_db.xlsx` 파일이 생성되었는지 확인
|
||||
3. 소프트웨어 > 영구/구독 탭 진입 시 "할당자" 테이블 헤더가 사라진 것 확인
|
||||
4. 관리 탭의 "사용자 관리" 아이콘 클릭 시, 해당 소프트웨어의 사용자를 등록하고 삭제할 수 있는 팝업 등장하는지 확인
|
||||
5. 사용자 아이콘을 클릭해 홍길동, 김철수 등록 후, 전체 엑셀 저장 혹은 다운로드 시 엑셀 파일 내의 '할당자' 열에 `홍길동,김철수` 로 잘 들어가는지 확인
|
||||
@@ -8,14 +8,9 @@
|
||||
<title>한맥가족 자산관리시스템</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css" />
|
||||
<link rel="stylesheet" href="/src/styles/common.css" />
|
||||
<link rel="stylesheet" href="/src/styles/login.css" />
|
||||
<link rel="stylesheet" href="/src/styles/guide.css" />
|
||||
<link rel="stylesheet" href="/src/styles/modal.css" />
|
||||
<link rel="stylesheet" href="/src/styles/dashboard.css" />
|
||||
<link rel="stylesheet" href="/src/styles/table.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
|
||||
<script src="/qrcode.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
BIN
label/DevExpress.Data.v14.1.dll
Normal file
BIN
label/DevExpress.Printing.v14.1.Core.dll
Normal file
BIN
label/DevExpress.Utils.v14.1.dll
Normal file
BIN
label/DevExpress.XtraEditors.v14.1.dll
Normal file
BIN
label/DevExpress.XtraGrid.v14.1.dll
Normal file
BIN
label/DevExpress.XtraLayout.v14.1.dll
Normal file
BIN
label/DevExpress.XtraPrinting.v14.1.dll
Normal file
BIN
label/LabelPrinter.exe
Normal file
BIN
label/Newtonsoft.Json.dll
Normal file
BIN
label/WebQuery.dll
Normal file
4
label/config.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[PRINT]
|
||||
FONT=8
|
||||
LEFT=143
|
||||
TOP=40
|
||||
BIN
label/de/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/de/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/es/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/ja/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/ru/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
7
label/tmp/file_1.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
자산번호 : 210312
|
||||
자산명 : 가을-PC(i5-12400F)
|
||||
공급사 : (주)가을디에스
|
||||
자산위치 : 지반부
|
||||
관리부서 : 전산
|
||||
사용자 : 박노석
|
||||
취득일자 : 2024-08-05
|
||||
BIN
label/tmp/file_1.txt - 바로 가기.lnk
Normal file
@@ -112,7 +112,7 @@
|
||||
"y": "32.01",
|
||||
"w": "40.87",
|
||||
"h": "6.24",
|
||||
"asset_id": null
|
||||
"asset_id": "9pvkqyi"
|
||||
}
|
||||
],
|
||||
"img/location_photo/IDC/서관203.png": [
|
||||
@@ -722,5 +722,21 @@
|
||||
"h": "6.75",
|
||||
"asset_id": "server_1779761946023_64"
|
||||
}
|
||||
],
|
||||
"img/location_photo/TDD_TEST_MAP.png": [
|
||||
{
|
||||
"x": "30.50",
|
||||
"y": "40.25",
|
||||
"w": "10.00",
|
||||
"h": "12.00",
|
||||
"asset_id": null
|
||||
},
|
||||
{
|
||||
"x": "50.00",
|
||||
"y": "60.00",
|
||||
"w": "5.00",
|
||||
"h": "5.00",
|
||||
"asset_id": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ITAM Map Coordinate Editor v3.0</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css" />
|
||||
<script src="/qrcode.min.js"></script>
|
||||
</head>
|
||||
<body style="margin: 0; display: flex; height: 100vh; overflow: hidden; font-family: sans-serif;">
|
||||
<body class="editor-body">
|
||||
|
||||
<!-- Left: File Selector -->
|
||||
<div class="file-sidebar" id="file-sidebar">
|
||||
@@ -22,7 +23,7 @@
|
||||
|
||||
<!-- Right: Control Panel -->
|
||||
<div class="sidebar">
|
||||
<h2>Map Editor <small style="font-size: 0.6em; color: #888;">v3.0</small></h2>
|
||||
<h2>Map Editor <small class="editor-version">v3.0</small></h2>
|
||||
<div class="current-path" id="current-path">파일을 선택하세요</div>
|
||||
<p>
|
||||
드래그하여 구역을 정의하세요. 저장 버튼을 누르면 즉시 시스템에 반영됩니다.
|
||||
@@ -30,9 +31,10 @@
|
||||
|
||||
<div class="box-list" id="box-list"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="btn-clear-all" class="btn btn-outline" style="height:38px;">전체 삭제</button>
|
||||
<button id="btn-save-server" class="btn btn-primary" style="height:38px;">서버에 즉시 저장</button>
|
||||
<div class="actions" style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<button id="btn-clear-all" class="btn btn-outline">전체 삭제</button>
|
||||
<button id="btn-print-map-qrs" class="btn btn-outline btn-primary">이 도면 QR 일괄인쇄</button>
|
||||
<button id="btn-save-server" class="btn btn-primary">서버에 즉시 저장</button>
|
||||
<div id="save-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
321
mobile.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>ITAM 모바일 실사 점검</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css" />
|
||||
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #09090b;
|
||||
--card: #18181b;
|
||||
--card-border: #27272a;
|
||||
--primary: #3b82f6;
|
||||
--primary-hover: #2563eb;
|
||||
--success: #10b981;
|
||||
--danger: #ef4444;
|
||||
--text: #f4f4f5;
|
||||
--text-muted: #a1a1aa;
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--card);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #60a5fa, #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--success);
|
||||
box-shadow: 0 0 8px var(--success);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
overflow-y: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Scanner Viewport */
|
||||
.scanner-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
border: 2px dashed var(--card-border);
|
||||
position: relative;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#reader {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
#reader video {
|
||||
object-fit: cover !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* Scan Laser Line Animation */
|
||||
.scan-laser {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(to right, transparent, var(--primary), transparent);
|
||||
animation: scan 2s linear infinite;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes scan {
|
||||
0% { top: 0%; }
|
||||
50% { top: 100%; }
|
||||
100% { top: 0%; }
|
||||
}
|
||||
|
||||
/* Bottom Info Card */
|
||||
.info-panel {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background-color: var(--card);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 16px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.badge-lock {
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
color: var(--primary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.badge-empty {
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
background-color: var(--primary);
|
||||
color: var(--text);
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-action.btn-danger {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: var(--danger);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.btn-action.btn-danger:hover {
|
||||
background-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
/* Manual Input Section */
|
||||
.manual-toggle {
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.manual-form {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
color: var(--text);
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
/* Feedbacks Overlay */
|
||||
.feedback-message {
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
display: none;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.feedback-success {
|
||||
background-color: rgba(16, 185, 129, 0.15);
|
||||
color: var(--success);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.feedback-error {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: var(--danger);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>ITAM 모바일 실사</h1>
|
||||
<div class="status-dot"></div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="scanner-container">
|
||||
<div id="reader"></div>
|
||||
<div class="scan-laser"></div>
|
||||
</div>
|
||||
|
||||
<div class="info-panel">
|
||||
<!-- 1. 위치 락 정보 -->
|
||||
<div class="info-section">
|
||||
<span class="info-label">현재 점검 위치 (Location)</span>
|
||||
<div class="info-value">
|
||||
<span id="loc-display" class="badge-empty">위치 QR 코드를 먼저 스캔하세요.</span>
|
||||
<button id="btn-unlock-loc" class="btn-action btn-danger" style="display: none;">해제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr style="border: 0; border-top: 1px solid var(--card-border); margin: 0.25rem 0;" />
|
||||
|
||||
<!-- 2. 자산 스캔 결과 및 피드백 -->
|
||||
<div id="scan-feedback" class="feedback-message"></div>
|
||||
|
||||
<!-- 3. 수동 입력 토글 및 양식 -->
|
||||
<div class="info-section">
|
||||
<span id="btn-toggle-manual" class="manual-toggle">카메라가 안 되나요? 수동 코드로 입력</span>
|
||||
<div id="manual-form" class="manual-form">
|
||||
<input type="text" id="manual-code-input" class="input-field" placeholder="위치 또는 자산 코드 입력" />
|
||||
<button id="btn-submit-manual" class="btn-action w-full">입력 확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Custom Confirmation Modal -->
|
||||
<div id="scan-confirm-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.75); z-index: 9999; justify-content: center; align-items: center; padding: 1.5rem; backdrop-filter: blur(4px);">
|
||||
<div style="background-color: var(--card); border: 1px solid var(--card-border); border-radius: 16px; padding: 1.5rem; width: 100%; max-width: 320px; display: flex; flex-direction: column; gap: 1.25rem; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4); animation: modalFadeIn 0.25s ease-out;">
|
||||
<h3 style="font-size: 1.1rem; font-weight: 700; color: var(--text); display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: var(--primary);"></span>
|
||||
실사 등록 확인
|
||||
</h3>
|
||||
<p id="confirm-modal-msg" style="font-size: 0.9rem; color: var(--text-muted); line-height: 1.6; word-break: keep-all;"></p>
|
||||
<div style="display: flex; gap: 0.50rem; margin-top: 0.25rem;">
|
||||
<button id="btn-confirm-cancel" class="btn-action btn-danger" style="flex: 1; padding: 0.75rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem;">취소</button>
|
||||
<button id="btn-confirm-ok" class="btn-action" style="flex: 1; padding: 0.75rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem;">등록</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module" src="/src/mobile-main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
291
package-lock.json
generated
@@ -14,9 +14,11 @@
|
||||
"iconv-lite": "^0.7.2",
|
||||
"lucide": "^0.364.0",
|
||||
"mysql2": "^3.22.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
@@ -774,11 +776,19 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
|
||||
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
@@ -801,6 +811,28 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
@@ -872,6 +904,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
@@ -885,6 +925,16 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
@@ -894,6 +944,22 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
|
||||
@@ -980,6 +1046,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
@@ -998,6 +1072,11 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
@@ -1030,6 +1109,11 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@@ -1187,6 +1271,18 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -1247,6 +1343,14 @@
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -1371,6 +1475,14 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
@@ -1383,6 +1495,17 @@
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
@@ -1575,6 +1698,39 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@@ -1584,6 +1740,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||
@@ -1601,6 +1765,14 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.9",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
||||
@@ -1643,6 +1815,22 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
|
||||
@@ -1682,6 +1870,19 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
|
||||
@@ -1794,6 +1995,11 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -1918,6 +2124,30 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@@ -1959,8 +2189,7 @@
|
||||
"version": "7.19.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
@@ -2040,6 +2269,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||
@@ -2058,6 +2292,19 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@@ -2084,6 +2331,44 @@
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"db-init": "node db_init.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
},
|
||||
@@ -21,6 +22,7 @@
|
||||
"iconv-lite": "^0.7.2",
|
||||
"lucide": "^0.364.0",
|
||||
"mysql2": "^3.22.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"xlsx": "^0.18.5"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 10 MiB |
|
Before Width: | Height: | Size: 6.3 MiB After Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 7.9 MiB After Width: | Height: | Size: 7.9 MiB |
|
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 4.7 MiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |