diff --git a/backup_db.js b/backup_db.js new file mode 100644 index 0000000..2687b14 --- /dev/null +++ b/backup_db.js @@ -0,0 +1,59 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; +import * as xlsx from 'xlsx'; +import fs from 'fs'; + +dotenv.config(); + +const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; + +async function backup() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('๐ Starting Database Backup Process...'); + + const tables = [ + 'asset_pc', 'asset_server', 'asset_storage', 'asset_remote', + 'asset_equipment', 'asset_office_supplies', 'asset_survey', 'asset_vip' + ]; + + const wb = xlsx.utils.book_new(); + + for (const table of tables) { + try { + // 1. Create table backup + await connection.query(`DROP TABLE IF EXISTS ${table}_backup`); + await connection.query(`CREATE TABLE ${table}_backup AS SELECT * FROM ${table}`); + console.log(`โ Table backup created: ${table} -> ${table}_backup`); + + // 2. Fetch data for Excel + const [rows] = await connection.query(`SELECT * FROM ${table}`); + if (rows.length > 0) { + const ws = xlsx.utils.json_to_sheet(rows); + // Sheet names max length is 31 chars + const sheetName = table.substring(0, 31); + xlsx.utils.book_append_sheet(wb, ws, sheetName); + } + } catch (e) { + console.warn(`โ ๏ธ Skipped ${table}: ${e.message}`); + } + } + + // 3. Write Excel file + const fileName = 'backupDB_20260608.xlsx'; + xlsx.writeFile(wb, fileName); + console.log(`โ Excel data exported successfully to ${fileName}`); + + await connection.end(); +} + +backup().catch(err => { + console.error('โ Backup Failed:', err); + process.exit(1); +}); diff --git a/check_network.js b/check_network.js new file mode 100644 index 0000000..85228d4 --- /dev/null +++ b/check_network.js @@ -0,0 +1,29 @@ +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 checkRemote() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('--- Checking asset_remote table ---'); + + const [columns] = await connection.query('DESCRIBE asset_remote'); + const cols = columns.map(c => c.Field); + console.log('Columns in asset_remote:', cols.join(', ')); + + const [count] = await connection.query('SELECT COUNT(*) as count FROM asset_remote WHERE remote_tool IS NOT NULL OR remote_id IS NOT NULL'); + console.log(`Rows with remote info (tool or id): ${count[0].count}`); + + await connection.end(); +} + +checkRemote().catch(console.error); diff --git a/drop_legacy.js b/drop_legacy.js new file mode 100644 index 0000000..c4b6ad5 --- /dev/null +++ b/drop_legacy.js @@ -0,0 +1,44 @@ +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 dropLegacyTables() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('๐งน Starting cleanup of obsolete legacy backup tables...'); + + const tablesToDrop = [ + 'asset_pc', 'asset_pc_backup', + 'asset_server', 'asset_server_backup', + 'asset_storage', 'asset_storage_backup', + 'asset_remote_backup', // IMPORTANT: DO NOT drop asset_remote! + 'asset_equipment', 'asset_equipment_backup', + 'asset_office_supplies', 'asset_office_supplies_backup', + 'asset_survey', 'asset_survey_backup', + 'asset_vip', 'asset_vip_backup', + 'asset_pc_parts' + ]; + + for (const table of tablesToDrop) { + try { + await connection.query(`DROP TABLE IF EXISTS ${table}`); + console.log(`โ Dropped table: ${table}`); + } catch (err) { + console.warn(`โ ๏ธ Failed to drop table ${table}: ${err.message}`); + } + } + + console.log('๐ Cleanup complete. Database is now lean and mean.'); + await connection.end(); +} + +dropLegacyTables().catch(console.error); diff --git a/migrate_schema.js b/migrate_schema.js new file mode 100644 index 0000000..4cd295c --- /dev/null +++ b/migrate_schema.js @@ -0,0 +1,197 @@ +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 migrateSchema() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('๐ Phase 1: Creating Normalized Tables & Migrating Data...'); + + try { + await connection.query('SET FOREIGN_KEY_CHECKS = 0'); + + // --- 1. Drop existing new tables if they exist --- + await connection.query('DROP TABLE IF EXISTS asset_core, asset_hardware, asset_location, asset_remote'); + + // --- 2. Create New Schema --- + await connection.query(` + CREATE TABLE asset_core ( + id VARCHAR(50) PRIMARY KEY, + asset_code VARCHAR(100) UNIQUE NOT NULL, + category VARCHAR(100), + asset_type VARCHAR(100), + asset_purpose VARCHAR(255), + service_type VARCHAR(50), + purchase_corp VARCHAR(100), + purchase_date VARCHAR(50), + purchase_amount VARCHAR(100), + purchase_vendor VARCHAR(255), + approval_document VARCHAR(255), + memo TEXT, + manager_primary VARCHAR(100), + manager_secondary VARCHAR(100), + current_dept VARCHAR(255), + previous_dept VARCHAR(255), + user_current VARCHAR(100), + previous_user VARCHAR(100), + emp_no VARCHAR(20), + user_position VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_hardware ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + hw_status VARCHAR(50), + model_name VARCHAR(255), + mainboard VARCHAR(255), + 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), + volume VARCHAR(100), + monitor_inch VARCHAR(50), + serial_num VARCHAR(100), + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_location ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + location VARCHAR(255), + location_detail VARCHAR(255), + location_photo VARCHAR(255), + loc_x VARCHAR(20), + loc_y VARCHAR(20), + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_remote ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + ip_address VARCHAR(100), + mac_address VARCHAR(100), + remote_tool VARCHAR(100), + remote_id VARCHAR(100), + remote_pw VARCHAR(100), + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query('SET FOREIGN_KEY_CHECKS = 1'); + console.log('โ Normalized tables created.'); + + // --- 3. Migrate Data from Legacy Tables --- + const legacyTables = ['asset_pc', 'asset_server', 'asset_storage', 'asset_remote', 'asset_equipment', 'asset_office_supplies', 'asset_survey', 'asset_vip']; + + let totalMigrated = 0; + + for (const table of legacyTables) { + try { + const [rows] = await connection.query(`SELECT * FROM ${table}`); + + for (const row of rows) { + // 3.1 Insert into asset_core + await connection.query(` + INSERT IGNORE INTO asset_core ( + id, asset_code, category, asset_type, asset_purpose, service_type, purchase_corp, purchase_date, + purchase_amount, purchase_vendor, approval_document, memo, manager_primary, manager_secondary, + current_dept, previous_dept, user_current, previous_user, emp_no, user_position, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [ + row.id, row.asset_code, row.category, row.asset_type, row.asset_purpose, row.service_type, + row.purchase_corp, row.purchase_date, row.purchase_amount, row.purchase_vendor, row.approval_document, + row.memo, row.manager_primary, row.manager_secondary, row.current_dept, row.previous_dept, + row.user_current, row.previous_user, row.emp_no, row.user_position, row.created_at + ]); + + // 3.2 Insert into asset_hardware (if hardware fields exist) + if (row.model_name || row.cpu || row.ram || row.hw_status) { + await connection.query(` + INSERT INTO asset_hardware ( + asset_id, hw_status, model_name, mainboard, os, cpu, ram, gpu, storage1, storage2, storage3, monitoring, price, volume, monitor_inch, serial_num + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [ + row.id, row.hw_status, row.model_name, row.mainboard, row.os, row.cpu, row.ram, row.gpu, + row.ssd_1 || row.hdd_1, row.ssd_2 || row.hdd_2, row.hdd_3, row.monitoring, row.price, + row.volume, row.monitor_inch, row.serial_num + ]); + } + + // 3.3 Insert into asset_location (if location fields exist) + if (row.location || row.location_detail) { + await connection.query(` + INSERT INTO asset_location ( + asset_id, location, location_detail, location_photo, loc_x, loc_y + ) VALUES (?, ?, ?, ?, ?, ?) + `, [ + row.id, row.location, row.location_detail, row.location_photo, row.loc_x, row.loc_y + ]); + } + + // 3.4 Insert into asset_remote (if network fields exist) + // Handle primary network interface + if (row.ip_address || row.mac_address || row.remote_tool) { + await connection.query(` + INSERT INTO asset_remote ( + asset_id, ip_address, mac_address, remote_tool, remote_id, remote_pw + ) VALUES (?, ?, ?, ?, ?, ?) + `, [ + row.id, row.ip_address, row.mac_address, row.remote_tool, row.remote_id, row.remote_pw + ]); + } + + // Handle secondary network interface (e.g., from server table) if it exists + if (row.ip_address_2 || row.remote_tool_2) { + await connection.query(` + INSERT INTO asset_remote ( + asset_id, ip_address, remote_tool, remote_id, remote_pw + ) VALUES (?, ?, ?, ?, ?) + `, [ + row.id, row.ip_address_2, row.remote_tool_2, row.remote_id_2, row.remote_pw_2 + ]); + } + + totalMigrated++; + } + console.log(`- Migrated ${rows.length} records from ${table}`); + } catch (err) { + console.warn(`- Skipping legacy table ${table}: ${err.message}`); + } + } + + console.log(`โ Phase 1 Data Migration Completed. Total Assets Migrated: ${totalMigrated}`); + + } catch (err) { + console.error('โ Migration Failed:', err); + } finally { + await connection.end(); + } +} + +migrateSchema(); diff --git a/migrate_v2_final.js b/migrate_v2_final.js new file mode 100644 index 0000000..53f0c5f --- /dev/null +++ b/migrate_v2_final.js @@ -0,0 +1,212 @@ +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 migrateV2() { + const connection = await mysql.createConnection({ + host: DB_HOST, + user: DB_USER, + password: DB_PASS, + database: DB_NAME, + port: parseInt(DB_PORT || '3306') + }); + + console.log('๐ Phase 2: Final Migration to Normalized V2 Schema...'); + + try { + await connection.query('SET FOREIGN_KEY_CHECKS = 0'); + + // 1. Create/Enhance Core Tables + console.log('1. Creating/Enhancing Tables...'); + + await connection.query('DROP TABLE IF EXISTS asset_core, asset_hardware, asset_location, asset_remote'); + + await connection.query(` + CREATE TABLE asset_core ( + id VARCHAR(50) PRIMARY KEY, + asset_code VARCHAR(100) UNIQUE NOT NULL, + category VARCHAR(100), + asset_type VARCHAR(100), + current_role VARCHAR(50) DEFAULT 'Normal' COMMENT 'Normal, Server, Personal, etc.', + asset_purpose VARCHAR(255), + service_type VARCHAR(50), + purchase_corp VARCHAR(100), + purchase_date VARCHAR(50), + purchase_amount VARCHAR(100), + purchase_vendor VARCHAR(255), + approval_document VARCHAR(255), + memo TEXT, + manager_primary VARCHAR(100), + manager_secondary VARCHAR(100), + current_dept VARCHAR(255), + previous_dept VARCHAR(255), + user_current VARCHAR(100), + previous_user VARCHAR(100), + emp_no VARCHAR(20), + user_position VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_hardware ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + hw_status VARCHAR(50), + model_name VARCHAR(255), + mainboard VARCHAR(255), + os VARCHAR(100), + cpu VARCHAR(255), + ram VARCHAR(100), + gpu VARCHAR(100), + storage1 VARCHAR(255), + storage2 VARCHAR(255), + storage3 VARCHAR(255), + storage4 VARCHAR(255), + monitoring VARCHAR(100), + price VARCHAR(100), + volume VARCHAR(100), + monitor_inch VARCHAR(50), + serial_num VARCHAR(100), + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_location ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + location VARCHAR(255), + location_detail VARCHAR(255), + location_photo VARCHAR(255), + loc_x VARCHAR(20), + loc_y VARCHAR(20), + is_active TINYINT(1) DEFAULT 1, + deactivated_at DATETIME NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + await connection.query(` + CREATE TABLE asset_remote ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + ip_address VARCHAR(100), + mac_address VARCHAR(100), + remote_tool VARCHAR(100), + remote_id VARCHAR(100), + remote_pw VARCHAR(100), + is_active TINYINT(1) DEFAULT 1, + deactivated_at DATETIME NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + console.log('โ V2 Schema tables created.'); + + // 2. Migration Logic + const legacyTables = [ + { name: 'asset_pc', defaultRole: 'Personal' }, + { name: 'asset_server', defaultRole: 'Server' }, + { name: 'asset_storage', defaultRole: 'Normal' }, + { name: 'asset_equipment', defaultRole: 'Normal' }, + { name: 'asset_office_supplies', defaultRole: 'Normal' }, + { name: 'asset_survey', defaultRole: 'Normal' }, + { name: 'asset_vip', defaultRole: 'Normal' }, + { name: 'asset_pc_parts', defaultRole: 'Normal' } + ]; + + let totalMigrated = 0; + + for (const tableInfo of legacyTables) { + const table = tableInfo.name; + try { + const [rows] = await connection.query(`SELECT * FROM ${table}`); + console.log(`- Migrating ${rows.length} records from ${table}...`); + + for (const row of rows) { + // 2.1 Insert into asset_core + const role = (table === 'asset_pc' && row.asset_type === '์๋ฒPC') ? 'Server' : tableInfo.defaultRole; + + await connection.query(` + INSERT IGNORE INTO asset_core ( + id, asset_code, category, asset_type, current_role, asset_purpose, service_type, purchase_corp, purchase_date, + purchase_amount, purchase_vendor, approval_document, memo, manager_primary, manager_secondary, + current_dept, previous_dept, user_current, previous_user, emp_no, user_position, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [ + row.id, row.asset_code, row.category, row.asset_type, role, row.asset_purpose, row.service_type, + row.purchase_corp, row.purchase_date, row.purchase_amount, row.purchase_vendor, row.approval_document, + row.memo, row.manager_primary, row.manager_secondary, row.current_dept, row.previous_dept, + row.user_current || row.current_user, row.previous_user, row.emp_no, row.user_position, row.created_at + ]); + + // 2.2 Insert into asset_hardware + await connection.query(` + INSERT INTO asset_hardware ( + asset_id, hw_status, model_name, mainboard, os, cpu, ram, gpu, storage1, storage2, storage3, storage4, monitoring, price, volume, monitor_inch, serial_num + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [ + row.id, row.hw_status, row.model_name, row.mainboard, row.os, row.cpu, row.ram, row.gpu, + row.ssd_1 || row.storage1, row.ssd_2 || row.storage2, row.hdd_1 || row.storage3, row.hdd_2, row.monitoring, row.price, + row.volume, row.monitor_inch, row.serial_num + ]); + + // 2.3 Insert into asset_location + if (row.location || row.location_detail) { + await connection.query(` + INSERT INTO asset_location ( + asset_id, location, location_detail, location_photo, loc_x, loc_y, is_active + ) VALUES (?, ?, ?, ?, ?, ?, 1) + `, [ + row.id, row.location, row.location_detail, row.location_photo, row.loc_x, row.loc_y + ]); + } + + // 2.4 Insert into asset_remote + // Primary Network + if (row.ip_address || row.mac_address || row.remote_tool) { + await connection.query(` + INSERT INTO asset_remote ( + asset_id, ip_address, mac_address, remote_tool, remote_id, remote_pw, is_active + ) VALUES (?, ?, ?, ?, ?, ?, 1) + `, [ + row.id, row.ip_address, row.mac_address, row.remote_tool, row.remote_id, row.remote_pw + ]); + } + + // Secondary Network (for servers) + if (row.ip_address_2 || row.remote_tool_2) { + await connection.query(` + INSERT INTO asset_remote ( + asset_id, ip_address, remote_tool, remote_id, remote_pw, is_active + ) VALUES (?, ?, ?, ?, ?, 1) + `, [ + row.id, row.ip_address_2, row.remote_tool_2, row.remote_id_2, row.remote_pw_2 + ]); + } + + totalMigrated++; + } + } catch (err) { + console.warn(`- Skipping table ${table}: ${err.message}`); + } + } + + await connection.query('SET FOREIGN_KEY_CHECKS = 1'); + console.log(`โ Phase 2 Data Migration Completed. Total Assets Migrated: ${totalMigrated}`); + + } catch (err) { + console.error('โ Migration Failed:', err); + } finally { + await connection.end(); + } +} + +migrateV2(); diff --git a/migrate_v4_network.js b/migrate_v4_network.js new file mode 100644 index 0000000..e61dbb8 --- /dev/null +++ b/migrate_v4_network.js @@ -0,0 +1,73 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306'), +}); + +async function migrate() { + const conn = await pool.getConnection(); + try { + console.log('1. Creating asset_remote_v4 table...'); + await conn.query(` + CREATE TABLE IF NOT EXISTS asset_remote_v4 ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id VARCHAR(50) NOT NULL, + net_type VARCHAR(20) NOT NULL, /* 'IP' or 'REMOTE' */ + net_name VARCHAR(100), /* e.g., '๊ธฐ๋ณธ๋ง', 'AnyDesk' */ + net_value1 VARCHAR(100), /* IP or ID */ + net_value2 VARCHAR(100), /* MAC or PW */ + is_active TINYINT(1) DEFAULT 1, + deactivated_at DATETIME NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset_id) REFERENCES asset_core(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `); + + console.log('2. Migrating data from asset_remote...'); + const [oldRows] = await conn.query('SELECT * FROM asset_remote WHERE is_active = 1'); + + let ipCount = 0; + let remoteCount = 0; + + for (const row of oldRows) { + // Migrating IP/MAC + if (row.ip_address || row.mac_address) { + await conn.query( + 'INSERT INTO asset_remote_v4 (asset_id, net_type, net_name, net_value1, net_value2, created_at) VALUES (?, ?, ?, ?, ?, ?)', + [row.asset_id, 'IP', '๊ธฐ๋ณธ๋ง', row.ip_address, row.mac_address, row.created_at] + ); + ipCount++; + } + // Migrating Remote + if (row.remote_tool || row.remote_id || row.remote_pw) { + await conn.query( + 'INSERT INTO asset_remote_v4 (asset_id, net_type, net_name, net_value1, net_value2, created_at) VALUES (?, ?, ?, ?, ?, ?)', + [row.asset_id, 'REMOTE', row.remote_tool, row.remote_id, row.remote_pw, row.created_at] + ); + remoteCount++; + } + } + + console.log(`Migrated ${ipCount} IP records and ${remoteCount} Remote records.`); + + console.log('3. Renaming tables...'); + await conn.query('DROP TABLE IF EXISTS asset_remote_legacy'); + await conn.query('RENAME TABLE asset_remote TO asset_remote_legacy, asset_remote_v4 TO asset_remote;'); + + console.log('โ Migration V4 (Remote) Complete.'); + } catch (e) { + console.error('Migration failed:', e); + } finally { + conn.release(); + pool.end(); + } +} + +migrate(); \ No newline at end of file diff --git a/migrate_v5_rename_remote.js b/migrate_v5_rename_remote.js new file mode 100644 index 0000000..2902006 --- /dev/null +++ b/migrate_v5_rename_remote.js @@ -0,0 +1,28 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + port: parseInt(process.env.DB_PORT || '3306'), +}); + +async function migrate() { + const conn = await pool.getConnection(); + try { + console.log('1. Renaming asset_network to asset_remote...'); + await conn.query('RENAME TABLE asset_network TO asset_remote'); + console.log('โ Table renamed successfully.'); + } catch (e) { + console.error('Migration failed:', e); + } finally { + conn.release(); + pool.end(); + } +} + +migrate(); diff --git a/server.js b/server.js index 1dacc03..b2772af 100644 --- a/server.js +++ b/server.js @@ -39,7 +39,7 @@ const CATEGORY_TABLE_MAP = { pc: 'asset_pc', server: 'asset_server', storage: 'asset_storage', - network: 'asset_network', + network: 'asset_remote', equipment: 'asset_equipment', officeSupplies: 'asset_office_supplies', survey: 'asset_survey', @@ -53,7 +53,7 @@ const CATEGORY_TABLE_MAP = { }; const ASSET_TABLES = [ - 'asset_pc', 'asset_server', 'asset_storage', 'asset_network', + 'asset_pc', 'asset_server', 'asset_storage', 'asset_remote', 'asset_equipment', 'asset_office_supplies', 'asset_survey', 'asset_vip' ]; @@ -115,7 +115,10 @@ app.get('/api/assets/master', async (req, res) => { s.hw_status, s.model_name, s.mainboard, s.os, s.cpu, s.ram, s.gpu, s.monitoring, s.price, s.monitor_inch, s.serial_num, l.location, l.location_detail, l.location_photo, l.loc_x, l.loc_y, - n.ip_address, n.mac_address, n.remote_tool, n.remote_id, n.remote_pw, + ( + SELECT JSON_ARRAYAGG(JSON_OBJECT('type', net_type, 'name', net_name, 'val1', net_value1, 'val2', net_value2)) + FROM asset_remote WHERE asset_id = c.id AND is_active = 1 + ) as remotes, ( SELECT JSON_ARRAYAGG(JSON_OBJECT('type', disk_type, 'capacity', capacity, 'unit', unit, 'slot', slot_no)) FROM asset_volume WHERE asset_id = c.id @@ -127,11 +130,6 @@ app.get('/api/assets/master', async (req, res) => { WHERE asset_id = c.id AND is_active = 1 ORDER BY created_at DESC LIMIT 1 ) - LEFT JOIN asset_network n ON n.id = ( - SELECT id FROM asset_network - WHERE asset_id = c.id AND is_active = 1 - ORDER BY created_at DESC LIMIT 1 - ) `); const catMap = { @@ -223,14 +221,36 @@ app.post('/api/asset/:category/save', async (req, res) => { } } - // 3.5 asset_network - if (asset.ip_address || asset.mac_address || asset.remote_tool) { - const [netActive] = await connection.query('SELECT * FROM asset_network WHERE asset_id = ? AND is_active = 1', [asset.id]); - const isChanged = netActive.length === 0 || netActive[0].ip_address !== asset.ip_address || netActive[0].mac_address !== asset.mac_address || netActive[0].remote_tool !== asset.remote_tool || netActive[0].remote_id !== asset.remote_id || netActive[0].remote_pw !== asset.remote_pw; - if (isChanged) { - await connection.query('UPDATE asset_network SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]); - await connection.query(`INSERT INTO asset_network (asset_id, ip_address, mac_address, remote_tool, remote_id, remote_pw, is_active) VALUES (?, ?, ?, ?, ?, ?, 1)`, - [asset.id, asset.ip_address, asset.mac_address, asset.remote_tool, asset.remote_id, asset.remote_pw]); + // 3.5 asset_remote (Dynamic Array Logic) + if (asset.remotes) { + try { + let nets = typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes; + if (Array.isArray(nets)) { + await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]); + for (const n of nets) { + if (n.type) { + await connection.query( + 'INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', + [asset.id, n.type, n.name || '', n.val1 || '', n.val2 || ''] + ); + } + } + } + } catch(e) { console.error('Remote data parse error', e); } + } else { + // Fallback for UI that hasn't sent the networks array yet + if (asset.ip_address || asset.mac_address || asset.remote_tool) { + const [netActive] = await connection.query('SELECT * FROM asset_remote WHERE asset_id = ? AND is_active = 1', [asset.id]); + const isChanged = netActive.length === 0 || netActive[0].net_value1 !== asset.ip_address || netActive[0].net_value2 !== asset.mac_address || netActive[0].net_name !== asset.remote_tool; + if (isChanged) { + await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]); + if (asset.ip_address || asset.mac_address) { + await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'IP', '๊ธฐ๋ณธ๋ง', asset.ip_address, asset.mac_address]); + } + if (asset.remote_tool || asset.remote_id || asset.remote_pw) { + await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'REMOTE', asset.remote_tool, asset.remote_id, asset.remote_pw]); + } + } } } @@ -272,7 +292,7 @@ app.delete('/api/asset/:category/:id', async (req, res) => { try { const connection = await pool.getConnection(); - // For asset_core, ON DELETE CASCADE will handle spec, location, network, volume + // For asset_core, ON DELETE CASCADE will handle spec, location, remote, volume await connection.query(`DELETE FROM ${table} WHERE id = ?`, [id]); connection.release(); console.log(`๐๏ธ [ASSET DELETE] Category: ${category}, ID: ${id}`); diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts index 9cd7916..5a59bb6 100644 --- a/src/components/Modal/HWModal.ts +++ b/src/components/Modal/HWModal.ts @@ -35,8 +35,9 @@ class HwAssetModal extends BaseModal {