Compare commits
134 Commits
34d99dc4b6
...
thoon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | |||
| c6515c1b5d | |||
| e128634e05 | |||
| 6848baae5f | |||
| a0570e88d4 | |||
| 502e5059b7 | |||
| c0ef52deac | |||
| aab1f91d3d | |||
| f656f0a439 | |||
| e77c4854cb | |||
| 1d32a0350b | |||
| d54997cd55 | |||
| fa8dec1780 | |||
| 309c400ee2 | |||
| 3db05f2939 | |||
| 2cb4b87c0a | |||
| 6ed2faee2d | |||
| 89d3ac2e89 | |||
| 9d19d8283e | |||
| b37981506e | |||
| abc531a41e | |||
| 8451101325 | |||
| 3e69e74bc9 | |||
| 73ef13f3a5 | |||
| 155570e8de | |||
| 723c4723f6 | |||
| a44283281f | |||
| fa87f383e2 | |||
| 6118141f6e | |||
| 119c799d1d | |||
| 05e23883b8 | |||
| 8c406fd0b8 | |||
| e678f9d653 | |||
| 132e37d0d3 | |||
| d6e75f8b2c | |||
| c35f57acab | |||
| 97cecb8b50 | |||
| b9d28736e2 | |||
| a4b620099c | |||
| b169176d57 | |||
| 56abdddbc7 | |||
| fd9e88d7c6 | |||
| 407b9ba531 | |||
| 55c43aa250 | |||
| 9186eb50ca | |||
| 8a3727ea61 | |||
| 0c1977f707 | |||
| 19e6be27de | |||
| accbbdc2fa | |||
| d3c4fa5e66 | |||
| 8c1cb6cf93 | |||
| 4810df212a | |||
| f5a84a77ef | |||
| 565802f55b | |||
| 10479aad7e | |||
| 95fbd3f606 | |||
| 207acbdecb | |||
| 164568843b | |||
| 29c7d5f3d8 | |||
| ce1ed40561 | |||
| 525dbd77d4 | |||
| 35c5b1e0fa | |||
| b87ca2854b | |||
| 2f88a0fae7 | |||
| 9a2c35e652 | |||
| 25ebaf4685 | |||
| 2b9c965c91 | |||
| 4b408b0640 | |||
| 3ab587d342 | |||
| 3b9b2ea598 | |||
| 05c565552a | |||
| 2ec9261c03 | |||
| 06f3baaa58 | |||
| eead43837d | |||
| 46422e8544 | |||
| a30f99f0ad | |||
| 9e8ab11f99 | |||
| 19d4222470 | |||
| db5c7a96a6 | |||
| 7d3d5ef281 | |||
| 9cd5d59bf8 | |||
| 590ddd0e85 |
13
.dockerignore
Normal file
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
|
||||||
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
DB_HOST=itam-mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=itam
|
||||||
|
DB_PASS=itam1234
|
||||||
|
DB_NAME=itam
|
||||||
|
PORT=3000
|
||||||
17
.env.example
Normal file
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
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
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
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
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"
|
||||||
|
|
||||||
|
- 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
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ dist/
|
|||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
backups/
|
||||||
|
mysql_data/
|
||||||
|
|||||||
12
Dockerfile.backend
Normal file
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
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
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
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
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>
|
|
||||||
36
README.md
36
README.md
@@ -9,6 +9,17 @@
|
|||||||
- 기존 동작 방식과 성능을 기준(Baseline)으로 삼고, 수정 후에도 **기존의 모든 기능이 무결하게 유지되는지 반드시 테스트하여 입증**한다.
|
- 기존 동작 방식과 성능을 기준(Baseline)으로 삼고, 수정 후에도 **기존의 모든 기능이 무결하게 유지되는지 반드시 테스트하여 입증**한다.
|
||||||
- 검증 결과를 바탕으로 "무엇을, 왜, 어떻게" 바꿀지 상세 보고 후, 사용자로부터 **'진행시켜'** 승인을 얻은 뒤에만 집행한다.
|
- 검증 결과를 바탕으로 "무엇을, 왜, 어떻게" 바꿀지 상세 보고 후, 사용자로부터 **'진행시켜'** 승인을 얻은 뒤에만 집행한다.
|
||||||
4. **선보고 후승인**: 모든 기능 수정 및 코드 변경 전에는 예상 방안을 먼저 보고하고 승인 절차를 거친다.
|
4. **선보고 후승인**: 모든 기능 수정 및 코드 변경 전에는 예상 방안을 먼저 보고하고 승인 절차를 거친다.
|
||||||
|
5. **DB 삭제 및 초기화 절대 엄금 (Strict DB Deletion Policy)**:
|
||||||
|
- 어떠한 경우에도 `DELETE`, `DROP`, `TRUNCATE` 등 데이터를 삭제하거나 테이블을 초기화하는 작업은 사전에 사용자에게 상세 사유를 보고하고 **명시적 승인**을 얻은 후에만 시행한다.
|
||||||
|
- 기존 데이터의 가치를 최우선으로 하며, 작업 전 백업 여부를 반드시 확인한다.
|
||||||
|
6. **RED–GREEN–Refactor 개발 원칙**:
|
||||||
|
- 모든 기능 개발과 버그 수정은 **RED → GREEN → Refactor** 순서로 진행한다.
|
||||||
|
- **RED**: 요구사항을 명확히 표현하는 테스트를 먼저 작성하고, 해당 테스트가 기능 미구현 또는 결함으로 인해 실패하는지 확인한다.
|
||||||
|
- **GREEN**: 실패한 테스트를 통과시키는 데 필요한 최소한의 코드만 구현하며, 불필요한 기능 추가나 구조 변경을 하지 않는다.
|
||||||
|
- **Refactor**: 관련 테스트와 기존 테스트가 모두 통과하는 상태에서만 중복 제거, 명칭 개선, 책임 분리 등 코드 구조를 개선하며 동작은 변경하지 않는다.
|
||||||
|
- 각 단계가 끝날 때마다 관련 테스트와 기존 기능의 회귀 여부를 검증한다.
|
||||||
|
- 테스트 작성이 현실적으로 불가능한 경우에는 그 사유와 대체 검증 방법을 먼저 보고하고 승인을 받은 후 진행한다.
|
||||||
|
- 본 원칙을 적용할 때에도 기존의 **선보고 후승인** 및 **외과 수술식 수정** 규칙을 준수한다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,29 +39,8 @@
|
|||||||
|
|
||||||
### 🎨 ITAM 시스템 디자인 가이드 (Design Guide)
|
### 🎨 ITAM 시스템 디자인 가이드 (Design Guide)
|
||||||
|
|
||||||
1. **디자인 철학 (Design Philosophy)**
|
디자인 일관성 및 시각적 원칙에 관한 상세 내용은 아래 문서를 참조하십시오.
|
||||||
* **Minimalist & Border-based**: 불필요한 박스(Card) 사용을 최소화하고, 정보의 구분은 간결한 라인(Border/Divider)을 활용하여 시각적 피로도를 낮춥니다.
|
|
||||||
* **Professional Achromatic**: 무채색(Black, White, Grey)을 기본으로 하여 정돈된 업무 환경을 제공합니다.
|
|
||||||
* **Green Accent**: 블루 대신 짙은 그린(`#1E5149`)을 포인트 컬러로 사용하여 차분한 전문성을 강조합니다.
|
|
||||||
|
|
||||||
2. **타이포그래피 (Typography)**
|
👉 **[디자인 가이드 바로가기 (design_rule.md)](./design_rule.md)**
|
||||||
* **Font Family**: `Pretendard` (전역 적용)
|
|
||||||
* **Letter Spacing**: `-0.02em` (약 -2%) 적용. 자간을 좁게 설정하여 밀도 있고 세련된 가독성을 확보합니다.
|
|
||||||
* **Weights**: 400(Regular), 500(Medium), 600(SemiBold), 700(Bold).
|
|
||||||
|
|
||||||
3. **컬러 팔레트 (Color Palette)**
|
|
||||||
* **Point Color**: `#1E5149` (Deep Green) - 강조, 활성화 상태, 주요 액션 버튼.
|
|
||||||
* **Text**: Main(`#111827` - Near Black), Muted(`#6B7280` - Grey).
|
|
||||||
* **Border/Divider**: `#E5E7EB` (Light Grey) - 정보 구분을 위한 얇은 실선.
|
|
||||||
* **Background**: `#FFFFFF` (White) / `#F9FAFB` (Off White).
|
|
||||||
|
|
||||||
4. **레이아웃 및 컴포넌트 규칙 (Layout Rules)**
|
|
||||||
* **Box-less Design**: 꼭 필요한 정보 묶음(데이터 그룹화 등)이 아니면 박스 형태의 테두리나 배경 사용을 지양합니다.
|
|
||||||
* **Line-based Division**: 섹션 간의 구분은 1px 두께의 얇은 실선(Border)을 통해 명확히 합니다.
|
|
||||||
* **Table**: 배경색이나 화려한 효과 없이 행(Row) 간의 얇은 구분선만 사용하여 데이터 본연에 집중하게 합니다.
|
|
||||||
* **Input/Button**: 입력 필드와 버튼은 최소한의 보더와 포인트 컬러만 사용하여 정갈하게 표현합니다.
|
|
||||||
* **Modal (모달 공통 규칙)**:
|
|
||||||
* **Header**: 짙은 그린(`#1E5149`) 배경에 화이트 텍스트를 사용하며, 우측 상단에 명확한 'X' 닫기 버튼을 배치합니다.
|
|
||||||
* **Interaction**: 사용자의 편의를 위해 `ESC` 키를 누르거나 모달 바깥 영역(Overlay)을 클릭하면 모달이 닫히도록 구현합니다.
|
|
||||||
* **Layout**: `detail.png` 기준의 2열 그리드 시스템을 권장하며, 하단 우측에 액션 버튼(닫기, 저장 등)을 배치합니다.
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -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)
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
|
||||||
File diff suppressed because it is too large
Load Diff
176
db_init.js
176
db_init.js
@@ -1,176 +0,0 @@
|
|||||||
import mysql from 'mysql2/promise';
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
|
|
||||||
|
|
||||||
async function initDB() {
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: DB_HOST,
|
|
||||||
user: DB_USER,
|
|
||||||
password: DB_PASS,
|
|
||||||
database: DB_NAME,
|
|
||||||
port: parseInt(DB_PORT || '3306'),
|
|
||||||
multipleStatements: true
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('🔄 DB 초기화 시작 (영문 표준 스키마 적용)...');
|
|
||||||
|
|
||||||
const tablesToDrop = [
|
|
||||||
'pc_assets', 'server_assets', 'storage_assets', 'equip_assets', 'mobile_assets',
|
|
||||||
'sw_sub_assets', 'sw_perm_assets', 'cloud_assets', 'sw_users', 'asset_logs'
|
|
||||||
];
|
|
||||||
for (const table of tablesToDrop) {
|
|
||||||
await connection.query(`DROP TABLE IF EXISTS ${table}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createHardwareTable = (tableName, comment) => `
|
|
||||||
CREATE TABLE ${tableName} (
|
|
||||||
id VARCHAR(50) PRIMARY KEY,
|
|
||||||
corp VARCHAR(100),
|
|
||||||
asset_code VARCHAR(100),
|
|
||||||
purchase_date VARCHAR(50),
|
|
||||||
type VARCHAR(50),
|
|
||||||
detail_purpose VARCHAR(50),
|
|
||||||
purpose VARCHAR(255),
|
|
||||||
details TEXT,
|
|
||||||
current_org VARCHAR(255),
|
|
||||||
prev_org VARCHAR(255),
|
|
||||||
location VARCHAR(255),
|
|
||||||
manager_main VARCHAR(100),
|
|
||||||
manager_sub VARCHAR(100),
|
|
||||||
ip_address VARCHAR(100),
|
|
||||||
remote_tool VARCHAR(100),
|
|
||||||
server_id VARCHAR(100),
|
|
||||||
server_pw VARCHAR(100),
|
|
||||||
model_name VARCHAR(255),
|
|
||||||
mainboard VARCHAR(255) COMMENT '메인보드',
|
|
||||||
os VARCHAR(100),
|
|
||||||
cpu VARCHAR(255),
|
|
||||||
ram VARCHAR(100),
|
|
||||||
gpu VARCHAR(100),
|
|
||||||
storage1 VARCHAR(255),
|
|
||||||
storage2 VARCHAR(255),
|
|
||||||
storage3 VARCHAR(255),
|
|
||||||
monitoring VARCHAR(100),
|
|
||||||
price VARCHAR(100),
|
|
||||||
remarks TEXT,
|
|
||||||
storage_location VARCHAR(255),
|
|
||||||
status VARCHAR(50),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`;
|
|
||||||
|
|
||||||
await connection.query(createHardwareTable('pc_assets', 'PC'));
|
|
||||||
await connection.query(createHardwareTable('server_assets', 'Server'));
|
|
||||||
await connection.query(createHardwareTable('storage_assets', 'Storage'));
|
|
||||||
await connection.query(createHardwareTable('equip_assets', 'Equipment'));
|
|
||||||
await connection.query(createHardwareTable('mobile_assets', 'Mobile'));
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE sw_sub_assets (
|
|
||||||
id VARCHAR(50) PRIMARY KEY,
|
|
||||||
corp VARCHAR(100) COMMENT '구매법인',
|
|
||||||
category VARCHAR(100) COMMENT '분야',
|
|
||||||
dept VARCHAR(100) COMMENT '부서',
|
|
||||||
product_name VARCHAR(255) COMMENT '제품명',
|
|
||||||
license_type VARCHAR(100) COMMENT '라이선스 유형',
|
|
||||||
quantity INT COMMENT '수량',
|
|
||||||
price VARCHAR(100) COMMENT '금액',
|
|
||||||
purchase_date VARCHAR(50) COMMENT '구매일',
|
|
||||||
start_date VARCHAR(50) COMMENT '시작일',
|
|
||||||
expiry_date VARCHAR(50) COMMENT '만료일',
|
|
||||||
vendor VARCHAR(255) COMMENT '구매업체',
|
|
||||||
remarks TEXT COMMENT '비고',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE sw_perm_assets (
|
|
||||||
id VARCHAR(50) PRIMARY KEY,
|
|
||||||
corp VARCHAR(100) COMMENT '구매법인',
|
|
||||||
category VARCHAR(100) COMMENT '분야',
|
|
||||||
dept VARCHAR(100) COMMENT '부서',
|
|
||||||
product_name VARCHAR(255) COMMENT '제품명',
|
|
||||||
license_key VARCHAR(255) COMMENT '라이선스 키',
|
|
||||||
quantity INT COMMENT '수량',
|
|
||||||
price VARCHAR(100) COMMENT '금액',
|
|
||||||
purchase_date VARCHAR(50) COMMENT '구매일',
|
|
||||||
start_date VARCHAR(50) COMMENT '시작일',
|
|
||||||
expiry_date VARCHAR(50) COMMENT '만료일',
|
|
||||||
vendor VARCHAR(255) COMMENT '구매업체',
|
|
||||||
remarks TEXT COMMENT '비고',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE cloud_assets (
|
|
||||||
id VARCHAR(50) PRIMARY KEY,
|
|
||||||
platform_name VARCHAR(100),
|
|
||||||
corp VARCHAR(100),
|
|
||||||
dept VARCHAR(100),
|
|
||||||
product_name VARCHAR(255),
|
|
||||||
account_name VARCHAR(255),
|
|
||||||
pay_method VARCHAR(100),
|
|
||||||
pay_day VARCHAR(50),
|
|
||||||
card_num VARCHAR(100),
|
|
||||||
monthly_fee VARCHAR(100),
|
|
||||||
remarks TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE sw_users (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
sw_id VARCHAR(50),
|
|
||||||
corp VARCHAR(100),
|
|
||||||
dept VARCHAR(100),
|
|
||||||
position VARCHAR(50),
|
|
||||||
user_name VARCHAR(100),
|
|
||||||
usage_period VARCHAR(100),
|
|
||||||
doc_name VARCHAR(255),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE asset_logs (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
asset_id VARCHAR(50),
|
|
||||||
log_date VARCHAR(50),
|
|
||||||
log_user VARCHAR(100),
|
|
||||||
details TEXT,
|
|
||||||
cost DECIMAL(15,2) DEFAULT 0,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await connection.query(`
|
|
||||||
CREATE TABLE ops_domain_assets (
|
|
||||||
id VARCHAR(50) PRIMARY KEY,
|
|
||||||
type VARCHAR(50) COMMENT '유형',
|
|
||||||
corp VARCHAR(100) COMMENT '법인',
|
|
||||||
service_name VARCHAR(255) COMMENT '서비스명',
|
|
||||||
domain_name VARCHAR(255) COMMENT '관리도메인',
|
|
||||||
start_date VARCHAR(50) COMMENT '시작일',
|
|
||||||
expiry_date VARCHAR(50) COMMENT '만료일',
|
|
||||||
price VARCHAR(100) COMMENT '금액',
|
|
||||||
manager_main VARCHAR(100) COMMENT '담당자',
|
|
||||||
manager_sub VARCHAR(100) COMMENT '담당자(부)',
|
|
||||||
remarks TEXT COMMENT '비고',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log('✅ 모든 테이블이 영문 표준 스키마로 재생성되었습니다.');
|
|
||||||
await connection.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
initDB().catch(err => {
|
|
||||||
console.error('❌ DB 초기화 실패:', err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
73
docker-compose.prod.yaml
Normal file
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:ro
|
||||||
|
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
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:ro
|
||||||
|
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
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:
|
||||||
55
docker/frontend/default.conf
Normal file
55
docker/frontend/default.conf
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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 / {
|
||||||
|
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
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
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
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. 로그 로테이션과 백업/복구 절차 문서화
|
||||||
60
docs/plans/PLAN_ASSET_HISTORY.md
Normal file
60
docs/plans/PLAN_ASSET_HISTORY.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 자산 이력 누적 관리 시스템 (Cumulative Asset History System) 구현 계획
|
||||||
|
|
||||||
|
본 문서는 자산의 라이프사이클(조직, 사용자, 용도, 상태 변동)을 체계적으로 추적하고 누적 관리하기 위한 기술적 설계 및 단계별 구현 계획을 담고 있습니다.
|
||||||
|
|
||||||
|
## 1. 목적
|
||||||
|
- 자산 정보 수정 시 중요 변경 사항을 자동으로 감지하여 이력(Log)화
|
||||||
|
- 과거부터 현재까지의 변동 사항을 타임라인 형태로 시각화하여 자산 흐름 파악
|
||||||
|
- 데이터 정합성을 위해 서버 측에서 변경 전/후 스냅샷 비교 방식 채택
|
||||||
|
|
||||||
|
## 2. 관리 대상 이력 (Watch Fields)
|
||||||
|
다음 항목의 변경이 발생할 경우 이력을 자동 생성합니다.
|
||||||
|
1. **조직 변동**: `current_dept` (현 사용조직) ↔ `previous_dept` 업데이트 포함
|
||||||
|
2. **사용자 변동**: `user_current` (현 사용자) ↔ `previous_user` 업데이트 포함
|
||||||
|
3. **용도 변경**: `asset_type`, `current_role` (예: 개인PC -> 공용PC)
|
||||||
|
4. **상태 변경**: `hw_status` (예: 운영 -> 수리, 재고 -> 폐기 등)
|
||||||
|
|
||||||
|
## 3. 기술 설계 (Technical Design)
|
||||||
|
|
||||||
|
### A. 데이터베이스 (DB)
|
||||||
|
- **대상 테이블**: `asset_history`
|
||||||
|
- **컬럼 구조 활용 및 보완**:
|
||||||
|
- `asset_id`: 대상 자산 식별자
|
||||||
|
- `event_type`: 변경 유형 (DEPT_CHANGE, USER_CHANGE, ROLE_CHANGE, STATUS_CHANGE)
|
||||||
|
- `details`: "상태 변경: 운영 -> 수리" 와 같이 읽기 쉬운 문자열 저장
|
||||||
|
- `cost`: 관련 비용 발생 시 기록 (수리비 등)
|
||||||
|
- `log_user`: 변경을 수행한 작업자
|
||||||
|
- `log_date`: 변경 발생 일시
|
||||||
|
|
||||||
|
### B. 백엔드 (Server-side Logic)
|
||||||
|
- **위치**: `server.js` 의 `POST /api/asset/:category/save` 엔드포인트
|
||||||
|
- **동작 흐름**:
|
||||||
|
1. **Snapshot**: 인서트/업데이트 수행 전, 기존 DB의 데이터를 `SELECT`하여 메모리에 저장.
|
||||||
|
2. **Comparison**: 요청된 신규 데이터와 기존 데이터를 필드별로 대조.
|
||||||
|
3. **Auto-logging**: 변경점이 발견되면 `asset_history` 테이블에 즉시 인서트.
|
||||||
|
4. **Transaction**: 모든 로그 생성이 자산 저장과 하나의 트랜잭션으로 묶여야 함.
|
||||||
|
|
||||||
|
### C. 프론트엔드 (UI/UX)
|
||||||
|
- **위치**: `HWModal.ts` 우측 `modal-history-area`
|
||||||
|
- **개선 사항**:
|
||||||
|
- `renderHistory()` 함수를 고도화하여 이벤트 타입별 아이콘/컬러 적용.
|
||||||
|
- "이전 값 ➔ 이후 값" 형태의 직관적인 레이아웃 도입.
|
||||||
|
- 스크롤을 통한 무제한 누적 이력 조회 지원.
|
||||||
|
|
||||||
|
## 4. 단계별 구현 로직
|
||||||
|
|
||||||
|
### 1단계: 서버 로직 고도화
|
||||||
|
- `server.js`에 비교 함수(`compareAndLog`) 구현.
|
||||||
|
- 각 자산 카테고리별 저장 로직에 비교 로직 삽입.
|
||||||
|
|
||||||
|
### 2단계: DB 데이터 마이그레이션 (필요시)
|
||||||
|
- 기존 자산의 `current_dept` 등을 `previous_dept`로 밀어내는 로직 점검.
|
||||||
|
|
||||||
|
### 3단계: UI 타임라인 렌더링 개선
|
||||||
|
- `modal.css`에 이력 전용 스타일(이벤트 뱃지 등) 추가.
|
||||||
|
- `HWModal.ts`에서 최신 로그를 실시간으로 다시 불러오는 로직 확인.
|
||||||
|
|
||||||
|
## 5. 검증 계획
|
||||||
|
- **자동 감지 테스트**: 상태 변경 후 저장 시 우측 이력에 즉시 한 줄이 추가되는지 확인.
|
||||||
|
- **다중 변경 테스트**: 조직과 사용자를 동시에 변경했을 때 두 개의 로그가 생성되는지 확인.
|
||||||
|
- **데이터 무결성**: 수정을 취소하거나 저장 실패 시 로그가 남지 않는지(Transaction) 확인.
|
||||||
48
docs/plans/design_rule.md
Normal file
48
docs/plans/design_rule.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 🎨 ITAM 시스템 디자인 가이드 (Design Guide)
|
||||||
|
|
||||||
|
본 문서는 ITAM(IT Asset Management System)의 시각적 일관성과 사용자 경험을 유지하기 위한 핵심 디자인 원칙을 정의합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 디자인 철학 (Design Philosophy)
|
||||||
|
* **Minimalist & Stark**: Vercel 스타일의 극도로 간결하고 현대적인 디자인을 지향합니다.
|
||||||
|
* **Achromatic Base**: 블랙(#171717)과 화이트를 기본으로 하며, 정보의 구분은 얇은 헤어라인(#ebebeb)을 사용합니다.
|
||||||
|
* **Fluid & Responsive**: 고정된 픽셀 대신 화면 크기에 비례하여 UI 밀도가 변하는 유동적 스케일링 시스템을 적용합니다.
|
||||||
|
|
||||||
|
### 2. 타이포그래피 및 자간 (Typography & Letter-spacing)
|
||||||
|
* **Font Family**: `Pretendard` 단일 폰트를 사용합니다.
|
||||||
|
* **Letter-spacing**: 모든 텍스트에 `-0.02em` (-2%) 자간을 적용하여 밀도 있는 가독성을 확보합니다.
|
||||||
|
* **Typography Scale**:
|
||||||
|
* **XS**: `clamp(10px, 1.2vmin + 0.2vw, 15px)` - 보조 텍스트
|
||||||
|
* **SM**: `clamp(12px, 1.4vmin + 0.3vw, 18px)` - 필터, 일반 라벨, 테이블 헤더
|
||||||
|
* **Base**: `clamp(14px, 1.6vmin + 0.4vw, 22px)` - 본문, 테이블 데이터
|
||||||
|
* **MD**: `clamp(18px, 2.5vmin + 0.5vw, 30px)` - 섹션 소제목
|
||||||
|
* **LG**: `clamp(24px, 4vmin + 0.6vw, 48px)` - 페이지 대제목
|
||||||
|
* **XL**: `clamp(32px, 6vmin + 0.8vw, 72px)` - 핵심 통계 지표
|
||||||
|
* **Layout Units**:
|
||||||
|
* **Header Height**: `clamp(50px, 8vmin, 90px)`
|
||||||
|
* **Base Spacing**: `clamp(0.75rem, 3vmin, 3rem)`
|
||||||
|
* **Radius**: `clamp(6px, 1.5vmin, 16px)`
|
||||||
|
|
||||||
|
### 3. 컬러 팔레트 (Vercel Stark Palette)
|
||||||
|
* **Primary**: `#171717` (Stark Black) - 텍스트, 주요 버튼, 강조 요소.
|
||||||
|
* **Secondary**: `#888888` (Mute) - 보조 텍스트, 비활성 아이콘.
|
||||||
|
* **Border**: `#ebebeb` (Hairline) - 정보 구분선.
|
||||||
|
* **Background**: `#ffffff` (Canvas), `#fafafa` (Soft), `#f5f5f5` (Soft 2).
|
||||||
|
* **Accents**: Blue(`#0070f3`), Orange(`#f5a623`), Danger(`#ee0000`).
|
||||||
|
|
||||||
|
### 4. 컴포넌트 및 레이아웃 규칙 (Component Rules)
|
||||||
|
* **Header & Navigation**:
|
||||||
|
* 상단 1열 통합 바 형태를 유지하며, GNB와 LNB를 동일 라인에 배치하여 공간을 효율적으로 사용합니다.
|
||||||
|
* **Unified Filter Bar**:
|
||||||
|
* 검색창과 필터는 상단 타이틀 바로 아래(기존 액션 버튼 라인)까지 올려서 배치합니다.
|
||||||
|
* **Action Group**: '자산 추가', '부품 마스터' 등의 주요 액션 버튼은 검색창과 같은 라인의 최우측에 정렬합니다.
|
||||||
|
* **Dashboard**:
|
||||||
|
* **Single-Screen View**: 1920*1080(또는 1920*919) 해상도에서 스크롤 없이 한 화면에 핵심 정보가 모두 보이도록 최적화합니다.
|
||||||
|
* **Fixed Charts**: 차트 내부 숫자나 요소에 애니메이션(`animation: false`) 및 플로팅 레이블을 배제하여 정적인 안정성을 확보합니다.
|
||||||
|
* **Footer**:
|
||||||
|
* 화면 최하단에 위치하며, 텍스트는 **우측 정렬(Right-aligned)**합니다.
|
||||||
|
* 상단에 1px 헤어라인 구분선을 가집니다.
|
||||||
|
* **Security & UX**:
|
||||||
|
* **Text Selection**: 사용자의 실수에 의한 UI 드래그 방지를 위해 입력창(`input`, `textarea`)을 제외한 전체 영역의 텍스트 선택을 차단합니다.
|
||||||
|
* **View Toggle**: '서버' 탭 등 특정 탭에서만 '목록보기' 체크박스를 통해 뷰를 전환하며, 그 외 화면은 리스트 중심의 UI를 제공합니다.
|
||||||
BIN
image 92.png
BIN
image 92.png
Binary file not shown.
|
Before Width: | Height: | Size: 135 KiB |
@@ -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. 사용자 아이콘을 클릭해 홍길동, 김철수 등록 후, 전체 엑셀 저장 혹은 다운로드 시 엑셀 파일 내의 '할당자' 열에 `홍길동,김철수` 로 잘 들어가는지 확인
|
|
||||||
23
index.html
23
index.html
@@ -5,26 +5,22 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ITAM 자산관리 ERP</title>
|
<title>한맥가족 자산관리시스템</title>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css" />
|
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/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/chart.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
|
||||||
|
<script src="/qrcode.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="app-layout">
|
<div class="app-layout" id="app-layout" style="display: none;">
|
||||||
<!-- Single-Line Integrated Header -->
|
<!-- Single-Line Integrated Header -->
|
||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
<div class="header-container" id="nav-container">
|
<div class="header-container" id="nav-container">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<img src="/image 92.png" alt="Logo" class="main-logo" />
|
<img src="/image 92.png" alt="Logo" class="main-logo" />
|
||||||
<h1>자산관리시스템<span class="sub-title">(Digital Asset Control Hub System)</span></h1>
|
<h1>한맥자산관리시스템</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation (GNB + LNB in same row) -->
|
<!-- Navigation (GNB + LNB in same row) -->
|
||||||
@@ -33,6 +29,14 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<div class="role-switcher" id="role-switcher">
|
||||||
|
<span class="role-label user active">실무자</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="role-toggle-checkbox">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<span class="role-label admin">관리자</span>
|
||||||
|
</div>
|
||||||
<button id="btn-admin-page" class="hidden"></button> <!-- JS 호환용 숨김 -->
|
<button id="btn-admin-page" class="hidden"></button> <!-- JS 호환용 숨김 -->
|
||||||
<button id="btn-open-guide-header" class="btn btn-outline" title="프로세스 가이드">
|
<button id="btn-open-guide-header" class="btn btn-outline" title="프로세스 가이드">
|
||||||
<i data-lucide="book-open"></i> 가이드
|
<i data-lucide="book-open"></i> 가이드
|
||||||
@@ -48,8 +52,7 @@
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="main-footer">
|
<footer class="main-footer">
|
||||||
<div id="secret-cloud-trigger" style="width: 20px; height: 20px; cursor: pointer; opacity: 0.1; background: #000; border-radius: 4px; position: absolute; left: 1rem;"></div>
|
<p>© 2026 BARON Consultant Co,Ltd. All rights reserved.</p>
|
||||||
<p>Powered by BARON Consultant Co,Ltd</p>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
BIN
label/DevExpress.Data.v14.1.dll
Normal file
BIN
label/DevExpress.Data.v14.1.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.Printing.v14.1.Core.dll
Normal file
BIN
label/DevExpress.Printing.v14.1.Core.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.Utils.v14.1.dll
Normal file
BIN
label/DevExpress.Utils.v14.1.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.XtraEditors.v14.1.dll
Normal file
BIN
label/DevExpress.XtraEditors.v14.1.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.XtraGrid.v14.1.dll
Normal file
BIN
label/DevExpress.XtraGrid.v14.1.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.XtraLayout.v14.1.dll
Normal file
BIN
label/DevExpress.XtraLayout.v14.1.dll
Normal file
Binary file not shown.
BIN
label/DevExpress.XtraPrinting.v14.1.dll
Normal file
BIN
label/DevExpress.XtraPrinting.v14.1.dll
Normal file
Binary file not shown.
BIN
label/LabelPrinter.exe
Normal file
BIN
label/LabelPrinter.exe
Normal file
Binary file not shown.
BIN
label/Newtonsoft.Json.dll
Normal file
BIN
label/Newtonsoft.Json.dll
Normal file
Binary file not shown.
BIN
label/WebQuery.dll
Normal file
BIN
label/WebQuery.dll
Normal file
Binary file not shown.
4
label/config.ini
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.Data.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/de/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.Utils.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/de/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/de/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.Data.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/es/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.Utils.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/es/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/es/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.Data.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/ja/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.Utils.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ja/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/ja/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.Data.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.Data.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
BIN
label/ru/DevExpress.Printing.v14.1.Core.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.Utils.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.Utils.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraEditors.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraGrid.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraLayout.v14.1.resources.dll
Normal file
Binary file not shown.
BIN
label/ru/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
BIN
label/ru/DevExpress.XtraPrinting.v14.1.resources.dll
Normal file
Binary file not shown.
7
label/tmp/file_1.txt
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
BIN
label/tmp/file_1.txt - 바로 가기.lnk
Normal file
Binary file not shown.
742
map_config.json
Normal file
742
map_config.json
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
{
|
||||||
|
"img/location_photo/IDC/서관205.png": [
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "1.53",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.27",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "10.35",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.27",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "19.06",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "27.89",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "36.71",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "45.64",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.32",
|
||||||
|
"asset_id": "server_1779761946023_23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "54.25",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.54",
|
||||||
|
"asset_id": "server_1779761946023_24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "63.29",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "72.00",
|
||||||
|
"w": "46.10",
|
||||||
|
"h": "6.32",
|
||||||
|
"asset_id": "server_1779761946023_21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "50.78",
|
||||||
|
"y": "81.92",
|
||||||
|
"w": "18.40",
|
||||||
|
"h": "15.58",
|
||||||
|
"asset_id": "server_1779761946023_17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "78.62",
|
||||||
|
"y": "81.92",
|
||||||
|
"w": "18.31",
|
||||||
|
"h": "15.58",
|
||||||
|
"asset_id": "server_1779761946023_20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/IDC/서관202.png": [
|
||||||
|
{
|
||||||
|
"x": "56.35",
|
||||||
|
"y": "64.02",
|
||||||
|
"w": "40.87",
|
||||||
|
"h": "6.24",
|
||||||
|
"asset_id": "server_1779761946023_9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.35",
|
||||||
|
"y": "71.57",
|
||||||
|
"w": "40.87",
|
||||||
|
"h": "6.24",
|
||||||
|
"asset_id": "server_1779761946023_10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.35",
|
||||||
|
"y": "79.17",
|
||||||
|
"w": "40.87",
|
||||||
|
"h": "6.24",
|
||||||
|
"asset_id": "server_1779761946023_26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.35",
|
||||||
|
"y": "86.66",
|
||||||
|
"w": "40.87",
|
||||||
|
"h": "6.24",
|
||||||
|
"asset_id": "server_1779761946023_8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.35",
|
||||||
|
"y": "32.01",
|
||||||
|
"w": "40.87",
|
||||||
|
"h": "6.24",
|
||||||
|
"asset_id": "9pvkqyi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/IDC/서관203.png": [
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "2.54",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "10.12",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "17.80",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "63.51",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "71.19",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.07",
|
||||||
|
"y": "87.70",
|
||||||
|
"w": "41.11",
|
||||||
|
"h": "6.52",
|
||||||
|
"asset_id": "server_1779761946023_25"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/IDC/서관204.png": [
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "2.73",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.27",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "10.38",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.27",
|
||||||
|
"asset_id": "server_1779761946023_3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "17.93",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "25.49",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "33.17",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "40.59",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "48.40",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "55.95",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "63.63",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "71.06",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "78.74",
|
||||||
|
"w": "47.80",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.87",
|
||||||
|
"y": "86.68",
|
||||||
|
"w": "18.99",
|
||||||
|
"h": "12.62",
|
||||||
|
"asset_id": "server_1779761946023_29"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/IDC/동관53.png": [
|
||||||
|
{
|
||||||
|
"x": "61.62",
|
||||||
|
"y": "3.08",
|
||||||
|
"w": "35.96",
|
||||||
|
"h": "7.90",
|
||||||
|
"asset_id": "server_1779761946023_13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "61.62",
|
||||||
|
"y": "12.68",
|
||||||
|
"w": "35.96",
|
||||||
|
"h": "7.90",
|
||||||
|
"asset_id": "server_1779761946023_15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "61.62",
|
||||||
|
"y": "21.75",
|
||||||
|
"w": "35.96",
|
||||||
|
"h": "7.90",
|
||||||
|
"asset_id": "server_1779761946023_22"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/IDC/동관54.png": [
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "2.57",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "10.38",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "27.15",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.62",
|
||||||
|
"asset_id": "server_1779761946023_12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "43.54",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "54.93",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "70.16",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "54.71",
|
||||||
|
"y": "79.51",
|
||||||
|
"w": "42.42",
|
||||||
|
"h": "6.50",
|
||||||
|
"asset_id": "server_1779761946023_28"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/한맥빌딩/MDF실/MDF_1.png": [
|
||||||
|
{
|
||||||
|
"x": "49.33",
|
||||||
|
"y": "14.99",
|
||||||
|
"w": "7.35",
|
||||||
|
"h": "11.22",
|
||||||
|
"asset_id": "cdp0e0c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "59.23",
|
||||||
|
"y": "14.99",
|
||||||
|
"w": "7.35",
|
||||||
|
"h": "11.22",
|
||||||
|
"asset_id": "emys9gb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "69.22",
|
||||||
|
"y": "14.99",
|
||||||
|
"w": "7.35",
|
||||||
|
"h": "11.22",
|
||||||
|
"asset_id": "vmbv3pj"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "79.12",
|
||||||
|
"y": "14.99",
|
||||||
|
"w": "7.35",
|
||||||
|
"h": "11.22",
|
||||||
|
"asset_id": "4fysk40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "88.97",
|
||||||
|
"y": "14.99",
|
||||||
|
"w": "7.35",
|
||||||
|
"h": "11.22",
|
||||||
|
"asset_id": "x6jaehn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.57",
|
||||||
|
"y": "34.11",
|
||||||
|
"w": "7.52",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": "t87p0l0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.80",
|
||||||
|
"y": "34.11",
|
||||||
|
"w": "7.52",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": "ywosxiv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "64.94",
|
||||||
|
"y": "34.11",
|
||||||
|
"w": "7.52",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "72.89",
|
||||||
|
"y": "34.11",
|
||||||
|
"w": "7.56",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "81.22",
|
||||||
|
"y": "34.06",
|
||||||
|
"w": "7.52",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "89.36",
|
||||||
|
"y": "34.06",
|
||||||
|
"w": "7.52",
|
||||||
|
"h": "11.44",
|
||||||
|
"asset_id": "tormk2l"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.57",
|
||||||
|
"y": "53.06",
|
||||||
|
"w": "9.06",
|
||||||
|
"h": "20.99",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "58.48",
|
||||||
|
"y": "53.06",
|
||||||
|
"w": "9.06",
|
||||||
|
"h": "20.99",
|
||||||
|
"asset_id": "server_1779761946023_30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "68.55",
|
||||||
|
"y": "53.06",
|
||||||
|
"w": "9.06",
|
||||||
|
"h": "20.99",
|
||||||
|
"asset_id": "server_1779761946023_31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "78.54",
|
||||||
|
"y": "53.06",
|
||||||
|
"w": "9.01",
|
||||||
|
"h": "20.99",
|
||||||
|
"asset_id": "server_1779761946023_32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "89.36",
|
||||||
|
"y": "53.22",
|
||||||
|
"w": "7.45",
|
||||||
|
"h": "10.11",
|
||||||
|
"asset_id": "TEMP-03g59cx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "89.36",
|
||||||
|
"y": "64.92",
|
||||||
|
"w": "7.45",
|
||||||
|
"h": "9.81",
|
||||||
|
"asset_id": "TEMP-06l8zjx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "48.57",
|
||||||
|
"y": "77.41",
|
||||||
|
"w": "9.18",
|
||||||
|
"h": "21.45",
|
||||||
|
"asset_id": "server_1779761946023_34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "58.56",
|
||||||
|
"y": "77.41",
|
||||||
|
"w": "9.23",
|
||||||
|
"h": "21.45",
|
||||||
|
"asset_id": "server_1779761946023_35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "68.63",
|
||||||
|
"y": "77.41",
|
||||||
|
"w": "9.06",
|
||||||
|
"h": "21.45",
|
||||||
|
"asset_id": "server_1779761946023_36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "78.71",
|
||||||
|
"y": "77.41",
|
||||||
|
"w": "8.98",
|
||||||
|
"h": "21.45",
|
||||||
|
"asset_id": "server_1779761946023_37"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/한맥빌딩/MDF실/MDF_2.png": [
|
||||||
|
{
|
||||||
|
"x": "56.59",
|
||||||
|
"y": "44.53",
|
||||||
|
"w": "40.65",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "1vbkbzr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.59",
|
||||||
|
"y": "54.80",
|
||||||
|
"w": "40.65",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "0ru63ay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.59",
|
||||||
|
"y": "65.94",
|
||||||
|
"w": "40.65",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "server_1779761946023_40"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/한맥빌딩/MDF실/MDF_3.png": [
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "13.20",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "23.57",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "8aeog58"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "34.57",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "ywosxiv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "44.69",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "1vbkbzr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "54.80",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "0ru63ay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "65.81",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": "server_1779761946023_40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "56.71",
|
||||||
|
"y": "76.05",
|
||||||
|
"w": "40.58",
|
||||||
|
"h": "6.90",
|
||||||
|
"asset_id": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/한맥빌딩/MDF실/MDF_4.png": [
|
||||||
|
{
|
||||||
|
"x": "52.36",
|
||||||
|
"y": "64.02",
|
||||||
|
"w": "44.60",
|
||||||
|
"h": "6.73",
|
||||||
|
"asset_id": "5tbpuy4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/기술개발센터/서버실/서버실_1.png": [
|
||||||
|
{
|
||||||
|
"x": "69.45",
|
||||||
|
"y": "3.30",
|
||||||
|
"w": "8.58",
|
||||||
|
"h": "11.45",
|
||||||
|
"asset_id": "server_1779761946023_41"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "79.05",
|
||||||
|
"y": "3.30",
|
||||||
|
"w": "12.02",
|
||||||
|
"h": "11.45",
|
||||||
|
"asset_id": "server_1779761946023_42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "90.16",
|
||||||
|
"y": "26.04",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_43"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "53.04",
|
||||||
|
"y": "52.91",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_44"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "62.36",
|
||||||
|
"y": "52.91",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_45"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "71.65",
|
||||||
|
"y": "52.91",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "80.87",
|
||||||
|
"y": "52.91",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_47"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "90.08",
|
||||||
|
"y": "52.91",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.11",
|
||||||
|
"asset_id": "server_1779761946023_48"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "43.78",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.50",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "19kai41"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "53.15",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "server_1779761946023_50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "62.36",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "server_1779761946023_51"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "71.36",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "server_1779761946023_52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "80.53",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.43",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "srlmyar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "89.77",
|
||||||
|
"y": "78.00",
|
||||||
|
"w": "8.50",
|
||||||
|
"h": "21.23",
|
||||||
|
"asset_id": "server_1779761946023_54"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"img/location_photo/기술개발센터/서버실/서버실_2.png": [
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "1.93",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_55"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "12.04",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "21.39",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_57"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "30.73",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_58"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "39.82",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "50.13",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "59.28",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_53"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "68.63",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_62"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "77.84",
|
||||||
|
"w": "47.19",
|
||||||
|
"h": "6.75",
|
||||||
|
"asset_id": "server_1779761946023_63"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": "49.60",
|
||||||
|
"y": "86.93",
|
||||||
|
"w": "47.19",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
44
map_editor.html
Normal file
44
map_editor.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<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 class="editor-body">
|
||||||
|
|
||||||
|
<!-- Left: File Selector -->
|
||||||
|
<div class="file-sidebar" id="file-sidebar">
|
||||||
|
<!-- Rendered by MapEditor.ts -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center: Main Editor -->
|
||||||
|
<div class="editor-container" id="container">
|
||||||
|
<div class="img-wrapper" id="wrapper">
|
||||||
|
<img src="" id="target-img" alt="Map Image">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Control Panel -->
|
||||||
|
<div class="sidebar">
|
||||||
|
<h2>Map Editor <small class="editor-version">v3.0</small></h2>
|
||||||
|
<div class="current-path" id="current-path">파일을 선택하세요</div>
|
||||||
|
<p>
|
||||||
|
드래그하여 구역을 정의하세요. 저장 버튼을 누르면 즉시 시스템에 반영됩니다.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="box-list" id="box-list"></div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<script type="module" src="/src/map-editor-main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
299
mobile.html
Normal file
299
mobile.html
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<!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>
|
||||||
|
|
||||||
|
<script type="module" src="/src/mobile-main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
291
package-lock.json
generated
291
package-lock.json
generated
@@ -14,9 +14,11 @@
|
|||||||
"iconv-lite": "^0.7.2",
|
"iconv-lite": "^0.7.2",
|
||||||
"lucide": "^0.364.0",
|
"lucide": "^0.364.0",
|
||||||
"mysql2": "^3.22.1",
|
"mysql2": "^3.22.1",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
}
|
}
|
||||||
@@ -774,11 +776,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.19.0"
|
"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": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
@@ -801,6 +811,28 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/aws-ssl-profiles": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
"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"
|
"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": {
|
"node_modules/cfb": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||||
@@ -885,6 +925,16 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/codepage": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||||
@@ -894,6 +944,22 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/content-disposition": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
|
"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": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
@@ -998,6 +1072,11 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.4.2",
|
"version": "17.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||||
@@ -1030,6 +1109,11 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/encodeurl": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
@@ -1187,6 +1271,18 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -1247,6 +1343,14 @@
|
|||||||
"is-property": "^1.0.2"
|
"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": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -1371,6 +1475,14 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/is-promise": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||||
@@ -1383,6 +1495,17 @@
|
|||||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/long": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
@@ -1575,6 +1698,39 @@
|
|||||||
"wrappy": "1"
|
"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": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -1584,6 +1740,14 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "8.4.2",
|
"version": "8.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||||
@@ -1601,6 +1765,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.9",
|
"version": "8.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
||||||
@@ -1643,6 +1815,22 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/qs": {
|
||||||
"version": "6.15.1",
|
"version": "6.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
|
||||||
@@ -1682,6 +1870,19 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/rollup": {
|
||||||
"version": "4.60.1",
|
"version": "4.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
|
||||||
@@ -1794,6 +1995,11 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
@@ -1918,6 +2124,30 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
@@ -1959,8 +2189,7 @@
|
|||||||
"version": "7.19.2",
|
"version": "7.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"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": {
|
"node_modules/wmf": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||||
@@ -2058,6 +2292,19 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
@@ -2084,6 +2331,44 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8"
|
"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"
|
"db-init": "node db_init.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
},
|
},
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"iconv-lite": "^0.7.2",
|
"iconv-lite": "^0.7.2",
|
||||||
"lucide": "^0.364.0",
|
"lucide": "^0.364.0",
|
||||||
"mysql2": "^3.22.1",
|
"mysql2": "^3.22.1",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/img/image_92.png
Normal file
BIN
public/img/image_92.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
BIN
public/img/location_photo/IDC/동관53.png
Normal file
BIN
public/img/location_photo/IDC/동관53.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 MiB |
BIN
public/img/location_photo/IDC/동관54.png
Normal file
BIN
public/img/location_photo/IDC/동관54.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 MiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user