From 7ee6446f0ff0452999a04d934e51eb1d9b46c2a9 Mon Sep 17 00:00:00 2001 From: choibk Date: Fri, 23 Jan 2026 16:52:14 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=84=A0=EC=8A=A4=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20DB=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CCTV_MODULE_GUIDE.md | 2 +- docs/DEPLOYMENT_GUIDE.md | 4 +- docs/LICENSE_MANAGER_MANUAL.md | 2 +- docs/task.md | 2 +- index.html | 3 +- package-lock.json | 8 +- package.json | 2 +- server/add_is_active_column.js | 2 +- server/config/public_key.pem | 8 ++ server/index.js | 26 ++++-- server/modules/cctv/streamRelay.js | 2 +- server/package-lock.json | 4 +- server/routes/auth.js | 4 +- server/routes/system.js | 35 ++++---- src/app/App.tsx | 4 +- src/pages/auth/LoginPage.tsx | 10 ++- src/platform/pages/BasicSettingsPage.tsx | 108 +++++++++++++++++++++++ src/widgets/layout/MainLayout.tsx | 6 +- tools/license_manager.cjs | 6 +- 19 files changed, 191 insertions(+), 47 deletions(-) create mode 100644 server/config/public_key.pem create mode 100644 src/platform/pages/BasicSettingsPage.tsx diff --git a/docs/CCTV_MODULE_GUIDE.md b/docs/CCTV_MODULE_GUIDE.md index 80993c2..d7f48a3 100644 --- a/docs/CCTV_MODULE_GUIDE.md +++ b/docs/CCTV_MODULE_GUIDE.md @@ -1,6 +1,6 @@ # CCTV 모바일 영상 및 카메라 설정 가이드 -본 문서는 SmartAsset CCTV 모니터링 모듈의 고급 설정 기능을 설명합니다. +본 문서는 Smart IMS CCTV 모니터링 모듈의 고급 설정 기능을 설명합니다. ## 1. 전송 방식 (Transport Mode) 오래된 카메라나 네트워크 환경에 따라 RTSP 데이터 전송 방식을 선택할 수 있습니다. diff --git a/docs/DEPLOYMENT_GUIDE.md b/docs/DEPLOYMENT_GUIDE.md index e85d901..47d2f4b 100644 --- a/docs/DEPLOYMENT_GUIDE.md +++ b/docs/DEPLOYMENT_GUIDE.md @@ -1,6 +1,6 @@ # 시스템 배포 가이드 (Deployment Guide) -본 문서는 빌드된 스마트어셋(SmartAsset) 솔루션을 실서버(Synology NAS 등)에 배포할 때 필요한 절차와 주의사항을 설명합니다. +본 문서는 빌드된 스마트 IMS(Smart IMS) 솔루션을 실서버(Synology NAS 등)에 배포할 때 필요한 절차와 주의사항을 설명합니다. ## 1. 배포 대상 폴더 및 파일 서버에 업로드해야 하는 핵심 구성 요소는 다음과 같습니다. @@ -52,7 +52,7 @@ 업로드가 완료된 후 서버 터미널(SSH)에서 다음 명령을 실행합니다. 1. **의존성 설치**: `cd server` 이동 후 `npm install` -2. **서비스 시작**: `pm2 start index.js --name "smartasset"` +2. **서비스 시작**: `pm2 start index.js --name "smartims"` 3. **상태 확인**: `pm2 status`를 통해 서버가 `online` 상태인지 확인합니다. --- diff --git a/docs/LICENSE_MANAGER_MANUAL.md b/docs/LICENSE_MANAGER_MANUAL.md index 4bcd0e6..62eb422 100644 --- a/docs/LICENSE_MANAGER_MANUAL.md +++ b/docs/LICENSE_MANAGER_MANUAL.md @@ -1,6 +1,6 @@ # 라이선스 관리자 매뉴얼 (최종본) -본 문서는 스마트어셋(SmartAsset) 시스템의 라이선스 관리 기능을 사용하는 방법을 설명합니다. 모든 라이선스 관리는 **RSA 비대칭 암호화** 방식을 따르며, `tools/license_manager.cjs` CLI 도구를 통해 수행됩니다. +본 문서는 스마트 IMS(Smart IMS) 시스템의 라이선스 관리 기능을 사용하는 방법을 설명합니다. 모든 라이선스 관리는 **RSA 비대칭 암호화** 방식을 따르며, `tools/license_manager.cjs` CLI 도구를 통해 수행됩니다. ## 1. 사전 준비 및 구독자 설정 (중요) 라이선스 키를 활성화하기 전에 서버가 어떤 고객사(구독자)에게 속해 있는지 식별하기 위해 **구독자 ID**를 설정해야 합니다. diff --git a/docs/task.md b/docs/task.md index c3d0972..484eb40 100644 --- a/docs/task.md +++ b/docs/task.md @@ -1,4 +1,4 @@ -# SmartAsset Project - Task & Feature Roadmap +# Smart IMS Project - Task & Feature Roadmap ## 1. Core Platform & Security - [x] **Project Initialization**: Created Vite + React + TypeScript base. diff --git a/index.html b/index.html index bf078b8..00318cc 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,10 @@ + - SmartAsset - 통합 자산관리 플랫폼 + Smart IMS - 통합 자산/공정 관리 플랫폼 diff --git a/package-lock.json b/package-lock.json index 468993b..21375aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "temp_app", - "version": "0.0.0", + "name": "smartims", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "temp_app", - "version": "0.0.0", + "name": "smartims", + "version": "0.1.0", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", diff --git a/package.json b/package.json index 8bfd6aa..2597af3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "temp_app", + "name": "smartims", "private": true, "version": "0.1.0", "type": "module", diff --git a/server/add_is_active_column.js b/server/add_is_active_column.js index ba895b3..45cb412 100644 --- a/server/add_is_active_column.js +++ b/server/add_is_active_column.js @@ -5,7 +5,7 @@ async function addColumn() { process.env.DB_HOST = process.env.DB_HOST || 'localhost'; process.env.DB_USER = process.env.DB_USER || 'root'; process.env.DB_PASSWORD = process.env.DB_PASSWORD || 'password'; // Fallback or assume env is loaded - process.env.DB_NAME = process.env.DB_NAME || 'smartasset_db'; + process.env.DB_NAME = process.env.DB_NAME || 'sokuree_platform_dev'; console.log(`Connecting to database: ${process.env.DB_NAME} at ${process.env.DB_HOST}`); diff --git a/server/config/public_key.pem b/server/config/public_key.pem new file mode 100644 index 0000000..b6bea89 --- /dev/null +++ b/server/config/public_key.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAjwHtoUUGe+Ib7KA9a2KV00c3B+4a0UTeDu9+jCg+fzyo6qD1ii0p +hJoASYkEVpHm5tMGJNMp/fJGyi3glPvfI8OFzs3JDgXWB6f4ao38fgAPtGE/x4Q2 +wsIHNvIY4scdFgokWYA1+k9//c4kszavOL8fRc85jOlkzUqzQLm3R1x34AVJEFdu +oo29vuHlFSzOqJ3c36cNUDJsya+h6Um96zPfM3UA2LLYsYVclr7Bem0jvSNv/sKt +hVcdG1QtlHTPAjQI4HZZf/51uIY/K2uXrVq3w4dplqMlLwIqDNb97Ire+Q+VaIe1 +n+GJBR5Jfa5soYsVjTwRId8VFvjcG0EJCQIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/server/index.js b/server/index.js index 27912be..2e67dd1 100644 --- a/server/index.js +++ b/server/index.js @@ -62,19 +62,33 @@ app.use('/uploads', express.static(uploadDir)); // Session Middleware app.use(session({ - key: 'smartasset_sid', - secret: process.env.SESSION_SECRET || 'smartasset_session_secret_key', + key: 'smartims_sid', + secret: process.env.SESSION_SECRET || 'smartims_session_secret_key', store: sessionStore, resave: false, - saveUninitialized: true, // Save new sessions even if empty (helps with some client handshake issues) + saveUninitialized: false, cookie: { - httpOnly: true, // Prevent JS access + httpOnly: true, secure: false, // Set true if using HTTPS - maxAge: 1000 * 60 * 60 * 24, // 1 day - sameSite: 'lax' // Recommended for better CSRF protection and reliability + maxAge: null, // Browser session by default + sameSite: 'lax' } })); +// Dynamic Session Timeout Middleware +app.use(async (req, res, next) => { + if (req.session && req.session.user) { + try { + const [rows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'session_timeout'"); + const timeoutMinutes = rows.length > 0 ? parseInt(rows[0].setting_value) : 60; + req.session.cookie.maxAge = timeoutMinutes * 60 * 1000; + } catch (err) { + console.error('Session timeout fetch error:', err); + } + } + next(); +}); + // Apply CSRF Protection app.use(csrfProtection); diff --git a/server/modules/cctv/streamRelay.js b/server/modules/cctv/streamRelay.js index 656fad0..dc3a6f9 100644 --- a/server/modules/cctv/streamRelay.js +++ b/server/modules/cctv/streamRelay.js @@ -127,7 +127,7 @@ class StreamRelay { const transportMode = process.env.CCTV_TRANSPORT_OVERRIDE || camera.transport_mode || 'tcp'; // 3. Unique User-Agent to prevent session conflicts - const userAgent = `SmartAsset-Relay-v1.0.8-${transportMode}`; + const userAgent = `SmartIMS-Relay-v1.0.8-${transportMode}`; // Quality Scaling let scaleFilter = []; diff --git a/server/package-lock.json b/server/package-lock.json index 6fff1f1..edf362f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "1.0.0", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "server", - "version": "1.0.0", + "version": "0.1.0", "license": "ISC", "dependencies": { "cors": "^2.8.5", diff --git a/server/routes/auth.js b/server/routes/auth.js index 8a4de01..e7be500 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -10,7 +10,7 @@ const { generateToken } = require('../middleware/csrfMiddleware'); // Key must be 32 bytes for aes-256-cbc // 'my_super_secret_key_manage_asset' is 32 chars? // let's use a simpler approach to ensure length on startup or fallback -const SECRET_KEY = process.env.ENCRYPTION_KEY || 'smartasset_secret_key_0123456789'; // 32 chars needed +const SECRET_KEY = process.env.ENCRYPTION_KEY || 'smartims_secret_key_0123456789'; // 32 chars needed // Ideally use a buffer from hex, but string is okay if 32 chars. // Let's pad it to ensure stability if env is missing. const keyBuffer = crypto.scryptSync(SECRET_KEY, 'salt', 32); @@ -127,7 +127,7 @@ router.post('/logout', (req, res) => { console.error('Logout error:', err); return res.status(500).json({ success: false, message: 'Logout failed' }); } - res.clearCookie('smartasset_sid'); // matching key in index.js + res.clearCookie('smartims_sid'); // matching key in index.js res.json({ success: true, message: 'Logged out' }); }); }); diff --git a/server/routes/system.js b/server/routes/system.js index 0866eec..6e38e79 100644 --- a/server/routes/system.js +++ b/server/routes/system.js @@ -8,7 +8,7 @@ const { generateLicense, verifyLicense } = require('../utils/licenseManager'); const { checkRemoteKey } = require('../utils/remoteLicense'); // Load Public Key for Verification -const publicKeyPath = path.join(__dirname, '../public_key.pem'); +const publicKeyPath = path.join(__dirname, '../config/public_key.pem'); let publicKey = null; try { if (fs.existsSync(publicKeyPath)) { @@ -20,25 +20,34 @@ try { console.error('Error loading public key:', e); } -// 0. Server Configuration (Subscriber ID) -router.get('/config', isAuthenticated, hasRole('admin'), async (req, res) => { +// 0. Server Configuration (Subscriber ID & Session Timeout) +router.get('/settings', isAuthenticated, hasRole('admin'), async (req, res) => { try { - const [rows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'subscriber_id'"); - res.json({ subscriber_id: rows.length > 0 ? rows[0].setting_value : '' }); + const [rows] = await db.query("SELECT setting_key, setting_value FROM system_settings WHERE setting_key IN ('subscriber_id', 'session_timeout')"); + const settings = {}; + rows.forEach(r => settings[r.setting_key] = r.setting_value); + + res.json({ + subscriber_id: settings.subscriber_id || '', + session_timeout: parseInt(settings.session_timeout) || 60 // Default 60 min + }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); -router.post('/config', isAuthenticated, hasRole('admin'), async (req, res) => { - const { subscriber_id } = req.body; - if (!subscriber_id) return res.status(400).json({ error: 'Subscriber ID is required' }); +router.post('/settings', isAuthenticated, hasRole('admin'), async (req, res) => { + const { subscriber_id, session_timeout } = req.body; try { - const sql = `INSERT INTO system_settings (setting_key, setting_value) VALUES ('subscriber_id', ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)`; - await db.query(sql, [subscriber_id]); - res.json({ message: 'Subscriber ID saved' }); + if (subscriber_id !== undefined) { + await db.query(`INSERT INTO system_settings (setting_key, setting_value) VALUES ('subscriber_id', ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)`, [subscriber_id]); + } + if (session_timeout !== undefined) { + await db.query(`INSERT INTO system_settings (setting_key, setting_value) VALUES ('session_timeout', ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)`, [session_timeout.toString()]); + } + res.json({ message: 'Settings saved' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); @@ -150,10 +159,6 @@ router.post('/modules/:code/activate', isAuthenticated, hasRole('admin'), async await db.query(sql, [code, names[code] || code, licenseKey, result.type, result.expiryDate, result.subscriberId]); - // 5. Update Issued License Status (if exists in issued_licenses table) - // This ensures CLI 'list' command reflects activation even if done via UI - await db.query("UPDATE issued_licenses SET status = 'ACTIVE' WHERE license_key = ?", [licenseKey]); - res.json({ success: true, message: 'Module activated', type: result.type, expiry: result.expiryDate }); } catch (err) { diff --git a/src/app/App.tsx b/src/app/App.tsx index f4ae2bc..3317b8c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -8,6 +8,7 @@ import { AssetRegisterPage } from '../modules/asset/pages/AssetRegisterPage'; import { AssetSettingsPage } from '../modules/asset/pages/AssetSettingsPage'; import { AssetDetailPage } from '../modules/asset/pages/AssetDetailPage'; import { UserManagementPage } from '../platform/pages/UserManagementPage'; +import { BasicSettingsPage } from '../platform/pages/BasicSettingsPage'; import { ProductionPage } from '../production/pages/ProductionPage'; import { MonitoringPage } from '../modules/cctv/pages/MonitoringPage'; import { AuthProvider } from '../shared/auth/AuthContext'; @@ -59,8 +60,9 @@ function App() { {/* Admin Routes */} } /> + } /> } /> - Admin (Coming Soon)} /> + } /> {/* Fallback */} diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx index da46577..88fc3d0 100644 --- a/src/pages/auth/LoginPage.tsx +++ b/src/pages/auth/LoginPage.tsx @@ -37,11 +37,11 @@ export function LoginPage() {
-

SmartAsset

-

통합 자산관리 시스템

+

Smart IMS

+

통합 자산/공정 관리 플랫폼

-
+
@@ -53,6 +53,7 @@ export function LoginPage() { onChange={(e) => setId(e.target.value)} placeholder="아이디를 입력하세요" required + autoComplete="one-time-code" />
@@ -68,6 +69,7 @@ export function LoginPage() { onChange={(e) => setPassword(e.target.value)} placeholder="비밀번호를 입력하세요" required + autoComplete="new-password" /> @@ -80,7 +82,7 @@ export function LoginPage() {
-

© 2026 SmartAsset System. All rights reserved.

+

© 2026 Smart IMS System. All rights reserved.

diff --git a/src/platform/pages/BasicSettingsPage.tsx b/src/platform/pages/BasicSettingsPage.tsx new file mode 100644 index 0000000..a52fffd --- /dev/null +++ b/src/platform/pages/BasicSettingsPage.tsx @@ -0,0 +1,108 @@ +import { useState, useEffect } from 'react'; +import { Card } from '../../shared/ui/Card'; +import { Button } from '../../shared/ui/Button'; +import { Input } from '../../shared/ui/Input'; +import { apiClient } from '../../shared/api/client'; +import { Save, Clock, Info } from 'lucide-react'; + +export function BasicSettingsPage() { + const [settings, setSettings] = useState({ + subscriber_id: '', + session_timeout: 60 + }); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + + useEffect(() => { + fetchSettings(); + }, []); + + const fetchSettings = async () => { + try { + const res = await apiClient.get('/system/settings'); + setSettings(res.data); + } catch (error) { + console.error('Failed to fetch settings', error); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + setSaving(true); + try { + await apiClient.post('/system/settings', settings); + alert('설정이 저장되었습니다.'); + } catch (error) { + console.error('Save failed', error); + alert('저장 중 오류가 발생했습니다.'); + } finally { + setSaving(false); + } + }; + + if (loading) return
로딩 중...
; + + return ( +
+
+

시스템 관리 - 기본 설정

+

플랫폼의 기본 운영 환경을 설정합니다.

+
+ +
+ +

+ + 식별 정보 +

+
+
+ + setSettings({ ...settings, subscriber_id: e.target.value })} + placeholder="예: SOKR-2024-001" + /> +

라이선스 활성화 시 사용되는 서버 고유 식별자입니다.

+
+
+
+ + +

+ + 보안 설정 +

+
+
+ +
+ setSettings({ ...settings, session_timeout: parseInt(e.target.value) })} + /> + 분 동안 활동이 없으면 자동으로 로그아웃됩니다. +
+

(최소 5분 ~ 최대 24시간)

+
+
+
+ +
+ +
+
+
+ ); +} diff --git a/src/widgets/layout/MainLayout.tsx b/src/widgets/layout/MainLayout.tsx index be0dd00..bd96b7a 100644 --- a/src/widgets/layout/MainLayout.tsx +++ b/src/widgets/layout/MainLayout.tsx @@ -28,7 +28,7 @@ export function MainLayout() {
- Platform + Smart IMS
@@ -49,6 +49,10 @@ export function MainLayout() { {expandedModules.includes('sys_mgmt') && (
+ + + 기본 설정 + 사용자 관리 diff --git a/tools/license_manager.cjs b/tools/license_manager.cjs index b8ea0ac..528dfcb 100644 --- a/tools/license_manager.cjs +++ b/tools/license_manager.cjs @@ -1,5 +1,5 @@ /** - * SmartAsset License Management CLI + * SmartIMS License Management CLI * * Usage: node tools/license_manager.cjs [args] * @@ -33,7 +33,7 @@ const dbConfig = { host: process.env.DB_HOST || 'localhost', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD, - database: process.env.DB_NAME || 'smartasset_db', + database: process.env.DB_NAME || 'sokuree_platform_dev', port: process.env.DB_PORT || 3306 }; @@ -105,7 +105,7 @@ async function main() { function printUsage() { console.log(` -SmartAsset License Manager +SmartIMS License Manager Usage: node tools/license_manager.cjs [args] Commands: