feat: update UI title, restore guide functionality, and simplify server list view
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
<title>ITAM 자산관리 ERP</title>
|
<title>ITAM 자산관리 ERP</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css" />
|
<link rel="stylesheet" href="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/common.css" />
|
||||||
|
<link rel="stylesheet" href="/src/styles/guide.css" />
|
||||||
<link rel="stylesheet" href="/src/styles/modal.css" />
|
<link rel="stylesheet" href="/src/styles/modal.css" />
|
||||||
<link rel="stylesheet" href="/src/styles/dashboard.css" />
|
<link rel="stylesheet" href="/src/styles/dashboard.css" />
|
||||||
<link rel="stylesheet" href="/src/styles/table.css" />
|
<link rel="stylesheet" href="/src/styles/table.css" />
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
<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">
|
||||||
<h1>HM <span>ITAM</span></h1>
|
<h1>HM <span>IT 자산관리 시스템</span></h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation (GNB + LNB in same row) -->
|
<!-- Navigation (GNB + LNB in same row) -->
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<button id="btn-open-guide-header" class="btn btn-outline" title="프로세스 가이드">
|
||||||
|
<i data-lucide="book-open"></i> 가이드
|
||||||
|
</button>
|
||||||
<button id="btn-download-template" class="btn btn-outline" title="통합 양식 다운로드">
|
<button id="btn-download-template" class="btn btn-outline" title="통합 양식 다운로드">
|
||||||
<i data-lucide="download"></i> 양식
|
<i data-lucide="download"></i> 양식
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -157,10 +157,20 @@ export function initGuide() {
|
|||||||
|
|
||||||
body.appendChild(overlay);
|
body.appendChild(overlay);
|
||||||
|
|
||||||
const openGuide = () => overlay.classList.add('active');
|
const openGuide = () => {
|
||||||
|
console.log('📖 Opening Guide Modal...');
|
||||||
|
overlay.classList.add('active');
|
||||||
|
};
|
||||||
const closeGuide = () => overlay.classList.remove('active');
|
const closeGuide = () => overlay.classList.remove('active');
|
||||||
|
|
||||||
document.getElementById('btn-open-guide-header')?.addEventListener('click', openGuide);
|
const triggerBtn = document.getElementById('btn-open-guide-header');
|
||||||
|
if (triggerBtn) {
|
||||||
|
console.log('✅ Guide trigger button found and bound.');
|
||||||
|
triggerBtn.addEventListener('click', openGuide);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ Guide trigger button (#btn-open-guide-header) not found in DOM.');
|
||||||
|
}
|
||||||
|
|
||||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeGuide(); });
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeGuide(); });
|
||||||
document.getElementById('btn-close-guide')?.addEventListener('click', closeGuide);
|
document.getElementById('btn-close-guide')?.addEventListener('click', closeGuide);
|
||||||
|
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ export function initHwModal(onSave: () => void, closeModalsCb: () => void) {
|
|||||||
if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; }
|
if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; }
|
||||||
const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`;
|
const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`http://localhost:3000/api/generate-asset-code?prefix=${prefix}`);
|
const res = await fetch(`http://172.16.40.100:3000/api/generate-asset-code?prefix=${prefix}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.nextCode) setFieldValue('hw-자산코드', data.nextCode);
|
if (data.nextCode) setFieldValue('hw-자산코드', data.nextCode);
|
||||||
} catch (err) { alert('자산번호 생성에 실패했습니다.'); }
|
} catch (err) { alert('자산번호 생성에 실패했습니다.'); }
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export function initSwModal(onSave: () => void, closeModalsCb: () => void) {
|
|||||||
if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; }
|
if (dateStr.length < 6) { alert('올바른 구매연월(YYYYMM)을 입력해주세요.'); return; }
|
||||||
const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`;
|
const prefix = `${typeCode}-${dateStr.substring(0, 6)}-`;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`http://localhost:3000/api/generate-asset-code?prefix=${prefix}`);
|
const res = await fetch(`http://172.16.40.100:3000/api/generate-asset-code?prefix=${prefix}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.nextCode) setFieldValue('sw-자산번호', data.nextCode);
|
if (data.nextCode) setFieldValue('sw-자산번호', data.nextCode);
|
||||||
} catch (err) { alert('자산번호 생성에 실패했습니다.'); }
|
} catch (err) { alert('자산번호 생성에 실패했습니다.'); }
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ export const state: AppState = {
|
|||||||
export async function loadMasterDataFromDB() {
|
export async function loadMasterDataFromDB() {
|
||||||
try {
|
try {
|
||||||
const endpoints = [
|
const endpoints = [
|
||||||
{ key: 'pc', url: 'http://localhost:3000/api/pc' },
|
{ key: 'pc', url: 'http://172.16.40.100:3000/api/pc' },
|
||||||
{ key: 'server', url: 'http://localhost:3000/api/server' },
|
{ key: 'server', url: 'http://172.16.40.100:3000/api/server' },
|
||||||
{ key: 'storage', url: 'http://localhost:3000/api/storage' },
|
{ key: 'storage', url: 'http://172.16.40.100:3000/api/storage' },
|
||||||
{ key: 'equip', url: 'http://localhost:3000/api/equip' },
|
{ key: 'equip', url: 'http://172.16.40.100:3000/api/equip' },
|
||||||
{ key: 'mobile', url: 'http://localhost:3000/api/mobile' },
|
{ key: 'mobile', url: 'http://172.16.40.100:3000/api/mobile' },
|
||||||
{ key: 'subSw', url: 'http://localhost:3000/api/sw/sub' },
|
{ key: 'subSw', url: 'http://172.16.40.100:3000/api/sw/sub' },
|
||||||
{ key: 'permSw', url: 'http://localhost:3000/api/sw/perm' },
|
{ key: 'permSw', url: 'http://172.16.40.100:3000/api/sw/perm' },
|
||||||
{ key: 'cloud', url: 'http://localhost:3000/api/cloud' },
|
{ key: 'cloud', url: 'http://172.16.40.100:3000/api/cloud' },
|
||||||
{ key: 'swUsers', url: 'http://localhost:3000/api/sw-users' },
|
{ key: 'swUsers', url: 'http://172.16.40.100:3000/api/sw-users' },
|
||||||
{ key: 'logs', url: 'http://localhost:3000/api/logs' }
|
{ key: 'logs', url: 'http://172.16.40.100:3000/api/logs' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await Promise.all(endpoints.map(e => fetch(e.url)));
|
const results = await Promise.all(endpoints.map(e => fetch(e.url)));
|
||||||
@@ -194,7 +194,7 @@ export function deleteHardwareAsset(assetId: string) {
|
|||||||
*/
|
*/
|
||||||
export async function saveSoftwareAsset(asset: SoftwareAsset) {
|
export async function saveSoftwareAsset(asset: SoftwareAsset) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:3000/api/software/save', {
|
const response = await fetch('http://172.16.40.100:3000/api/software/save', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(asset)
|
body: JSON.stringify(asset)
|
||||||
@@ -223,7 +223,7 @@ export async function saveSoftwareAsset(asset: SoftwareAsset) {
|
|||||||
*/
|
*/
|
||||||
export async function deleteSoftwareAsset(type: string, id: string) {
|
export async function deleteSoftwareAsset(type: string, id: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:3000/api/asset/${type}/${id}`, {
|
const response = await fetch(`http://172.16.40.100:3000/api/asset/${type}/${id}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export function formatPrice(value: string | number): string {
|
|||||||
/**
|
/**
|
||||||
* HTML 배지 생성 (정/부 담당자, 원격도구 등)
|
* HTML 배지 생성 (정/부 담당자, 원격도구 등)
|
||||||
*/
|
*/
|
||||||
export function createBadge(text: string, bgColor: string): string {
|
export function createBadge(text: string, type: 'primary' | 'muted' | 'success' | 'danger' = 'primary'): string {
|
||||||
return `<span style="background:${bgColor}; color:white; font-size:10px; padding:1px 4px; border-radius:3px; font-weight:700; margin-right:4px; display:inline-block; line-height:1.2;">${text}</span>`;
|
return `<span class="badge badge-${type}">${text}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
22
src/main.ts
22
src/main.ts
@@ -8,6 +8,7 @@ import { initHwModal, openHwModal } from './components/Modal/HWModal';
|
|||||||
import { initSwModal, openSwModal } from './components/Modal/SWModal';
|
import { initSwModal, openSwModal } from './components/Modal/SWModal';
|
||||||
import { initSwUserModal } from './components/Modal/SWUserModal';
|
import { initSwUserModal } from './components/Modal/SWUserModal';
|
||||||
import { initDashboardDetailModal } from './components/Modal/DashboardDetailModal';
|
import { initDashboardDetailModal } from './components/Modal/DashboardDetailModal';
|
||||||
|
import { initGuide } from './components/Guide';
|
||||||
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw } from 'lucide';
|
import { createIcons, Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw } from 'lucide';
|
||||||
|
|
||||||
// --- DB 저장을 위한 세분화된 헬퍼 함수들 ---
|
// --- DB 저장을 위한 세분화된 헬퍼 함수들 ---
|
||||||
@@ -25,15 +26,15 @@ async function apiBatchSave(url: string, data: any[], label: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const savePcToDB = () => apiBatchSave('http://localhost:3000/api/pc/batch', state.masterData.pc, '개인PC');
|
const savePcToDB = () => apiBatchSave('http://172.16.40.100:3000/api/pc/batch', state.masterData.pc, '개인PC');
|
||||||
const saveServerToDB = () => apiBatchSave('http://localhost:3000/api/server/batch', state.masterData.server, '서버');
|
const saveServerToDB = () => apiBatchSave('http://172.16.40.100:3000/api/server/batch', state.masterData.server, '서버');
|
||||||
const saveStorageToDB = () => apiBatchSave('http://localhost:3000/api/storage/batch', state.masterData.storage, '스토리지');
|
const saveStorageToDB = () => apiBatchSave('http://172.16.40.100:3000/api/storage/batch', state.masterData.storage, '스토리지');
|
||||||
const saveEquipToDB = () => apiBatchSave('http://localhost:3000/api/equip/batch', state.masterData.equip, '전산비품');
|
const saveEquipToDB = () => apiBatchSave('http://172.16.40.100:3000/api/equip/batch', state.masterData.equip, '전산비품');
|
||||||
const saveMobileToDB = () => apiBatchSave('http://localhost:3000/api/mobile/batch', state.masterData.mobile, '모바일기기');
|
const saveMobileToDB = () => apiBatchSave('http://172.16.40.100:3000/api/mobile/batch', state.masterData.mobile, '모바일기기');
|
||||||
const saveSubSwToDB = () => apiBatchSave('http://localhost:3000/api/sw/sub/batch', state.masterData.subSw, '구독SW');
|
const saveSubSwToDB = () => apiBatchSave('http://172.16.40.100:3000/api/sw/sub/batch', state.masterData.subSw, '구독SW');
|
||||||
const savePermSwToDB = () => apiBatchSave('http://localhost:3000/api/sw/perm/batch', state.masterData.permSw, '영구SW');
|
const savePermSwToDB = () => apiBatchSave('http://172.16.40.100:3000/api/sw/perm/batch', state.masterData.permSw, '영구SW');
|
||||||
const saveCloudToDB = () => apiBatchSave('http://localhost:3000/api/cloud/batch', state.masterData.cloud, '클라우드');
|
const saveCloudToDB = () => apiBatchSave('http://172.16.40.100:3000/api/cloud/batch', state.masterData.cloud, '클라우드');
|
||||||
const saveSwUsersToDB = () => apiBatchSave('http://localhost:3000/api/sw-users/batch', state.masterData.swUsers, 'SW사용자');
|
const saveSwUsersToDB = () => apiBatchSave('http://172.16.40.100:3000/api/sw-users/batch', state.masterData.swUsers, 'SW사용자');
|
||||||
|
|
||||||
// 모든 하드웨어 DB 동기화
|
// 모든 하드웨어 DB 동기화
|
||||||
async function saveAllHardwareToDB() {
|
async function saveAllHardwareToDB() {
|
||||||
@@ -87,6 +88,7 @@ function initApp() {
|
|||||||
}, closeAllModals);
|
}, closeAllModals);
|
||||||
|
|
||||||
initDashboardDetailModal();
|
initDashboardDetailModal();
|
||||||
|
initGuide(); // 가이드 초기화 추가
|
||||||
} catch (e) { console.error('❌ Initialization failed:', e); }
|
} catch (e) { console.error('❌ Initialization failed:', e); }
|
||||||
|
|
||||||
// 초기 로드 시 대시보드 렌더링
|
// 초기 로드 시 대시보드 렌더링
|
||||||
@@ -149,7 +151,7 @@ function initApp() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
createIcons({
|
createIcons({
|
||||||
icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw }
|
icons: { Download, Upload, FileSpreadsheet, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, History, RefreshCcw, BookOpen }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,3 +169,25 @@ body {
|
|||||||
|
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
.text-nowrap { white-space: nowrap; }
|
.text-nowrap { white-space: nowrap; }
|
||||||
|
|
||||||
|
/* --- Utility Styles --- */
|
||||||
|
.badge {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.badge-primary { background-color: var(--primary-color); color: white; }
|
||||||
|
.badge-muted { background-color: #9CA3AF; color: white; }
|
||||||
|
|
||||||
|
.text-tag {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-bold { font-weight: 700; }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { state } from '../../core/state';
|
import { state } from '../../core/state';
|
||||||
import { openHwModal } from '../../components/Modal/HWModal';
|
import { openHwModal } from '../../components/Modal/HWModal';
|
||||||
import { formatInline, createBadge, sortAssets } from '../../core/utils';
|
import { formatInline, createBadge, sortAssets } from '../../core/utils';
|
||||||
import { createIcons, RefreshCcw } from 'lucide';
|
import { createIcons, RefreshCcw, Edit2 } from 'lucide';
|
||||||
|
|
||||||
export function renderServerList(container: HTMLElement) {
|
export function renderServerList(container: HTMLElement) {
|
||||||
const fullList = sortAssets(state.masterData.server);
|
const fullList = sortAssets(state.masterData.server);
|
||||||
@@ -33,7 +33,7 @@ export function renderServerList(container: HTMLElement) {
|
|||||||
const tableWrapper = document.createElement('div');
|
const tableWrapper = document.createElement('div');
|
||||||
tableWrapper.className = 'table-container';
|
tableWrapper.className = 'table-container';
|
||||||
const table = document.createElement('table');
|
const table = document.createElement('table');
|
||||||
table.innerHTML = `<thead><tr><th>No</th><th>구매법인</th><th>현 사용조직</th><th>자산번호</th><th>용도</th><th>상세</th><th>설치위치</th><th>담당자</th><th>IP주소</th><th>모델명</th><th>OS</th><th>CPU/RAM</th><th>Storage</th><th>관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
table.innerHTML = `<thead><tr><th>No</th><th>구매법인</th><th>현 사용조직</th><th>자산번호</th><th>용도</th><th>상세</th><th>설치위치</th><th>담당자</th><th>관리</th></tr></thead><tbody id="dynamic-tbody"></tbody>`;
|
||||||
|
|
||||||
tableWrapper.appendChild(table);
|
tableWrapper.appendChild(table);
|
||||||
container.appendChild(tableWrapper);
|
container.appendChild(tableWrapper);
|
||||||
@@ -57,7 +57,7 @@ export function renderServerList(container: HTMLElement) {
|
|||||||
|
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
tbody.innerHTML = `<tr><td colspan="14" style="text-align:center; padding: 3rem; color: var(--text-muted);">검색 결과가 없습니다.</td></tr>`;
|
tbody.innerHTML = `<tr><td colspan="9" style="text-align:center; padding: 3rem; color: var(--text-muted);">검색 결과가 없습니다.</td></tr>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,27 +67,23 @@ export function renderServerList(container: HTMLElement) {
|
|||||||
|
|
||||||
const mainManager = asset.담당자_정 || '';
|
const mainManager = asset.담당자_정 || '';
|
||||||
const subManager = asset.담당자_부 || '';
|
const subManager = asset.담당자_부 || '';
|
||||||
const managerHtml = [mainManager ? `${createBadge('정', '#1E5149')} ${mainManager}` : '', subManager ? `${createBadge('부', '#9CA3AF')} ${subManager}` : ''].filter(v => v !== '').join(' / ');
|
const managerHtml = [
|
||||||
|
mainManager ? `${createBadge('정', 'primary')} ${mainManager}` : '',
|
||||||
const ipInfo = [asset.IP주소, asset.IP2].filter(v => v).join(' / ');
|
subManager ? `${createBadge('부', 'muted')} ${subManager}` : ''
|
||||||
const cpuRam = [asset.CPU, asset.RAM].filter(v => v).join(' / ');
|
].filter(v => v !== '').join(' / ');
|
||||||
const storage = [asset.SSD1, asset.SSD2].filter(v => v).join(' / ');
|
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${idx+1}</td>
|
<td style="text-align:center;">${idx+1}</td>
|
||||||
<td>${asset.법인}</td>
|
<td style="text-align:center;">${asset.법인}</td>
|
||||||
<td>${asset.현사용조직||''}</td>
|
<td style="text-align:center;">${asset.현사용조직||'-'}</td>
|
||||||
<td>${asset.자산코드}</td>
|
<td>${asset.자산코드}</td>
|
||||||
<td>${formatInline(asset.용도)}</td>
|
<td>${formatInline(asset.용도)}</td>
|
||||||
<td>${formatInline(asset.상세)}</td>
|
<td>${formatInline(asset.상세)}</td>
|
||||||
<td>${formatInline(asset.위치)}</td>
|
<td>${formatInline(asset.위치)}</td>
|
||||||
<td>${managerHtml}</td>
|
<td>${managerHtml}</td>
|
||||||
<td>${formatInline(ipInfo)}</td>
|
<td style="text-align:center;">
|
||||||
<td>${asset.모델명||''}</td>
|
<button class="btn-icon" title="수정"><i data-lucide="edit-2"></i></button>
|
||||||
<td>${asset.OS||''}</td>
|
</td>
|
||||||
<td>${formatInline(cpuRam)}</td>
|
|
||||||
<td>${formatInline(storage)}</td>
|
|
||||||
<td><button class="btn btn-outline btn-sm">수정</button></td>
|
|
||||||
`;
|
`;
|
||||||
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openHwModal(asset, 'view'); });
|
tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openHwModal(asset, 'view'); });
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
@@ -102,8 +98,8 @@ export function renderServerList(container: HTMLElement) {
|
|||||||
(document.getElementById('filter-corp') as HTMLSelectElement).value = '';
|
(document.getElementById('filter-corp') as HTMLSelectElement).value = '';
|
||||||
(document.getElementById('filter-org-unit') as HTMLSelectElement).value = '';
|
(document.getElementById('filter-org-unit') as HTMLSelectElement).value = '';
|
||||||
updateTable();
|
updateTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
updateTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
updateTable();
|
||||||
|
createIcons({ icons: { RefreshCcw, Edit2 } });
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user