- FastAPI 라우터에서 슬래시 문제로 인한 307 리다이렉트 수정 - Nginx 프록시 설정에서 경로 중복 문제 해결 - 계정 관리 시스템 구현 (로그인, 사용자 관리, 권한 설정) - 노트북 연결 기능 수정 (notebook_id 필드 추가) - 메모 트리 UI 개선 (수평 레이아웃, 드래그 기능 제거) - 헤더 UI 개선 및 고정 위치 설정 - 백업/복원 스크립트 추가 - PDF 미리보기 토큰 인증 지원
521 lines
29 KiB
HTML
521 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>사용자 관리 - Document Server</title>
|
|
|
|
<!-- Tailwind CSS -->
|
|
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
|
|
|
|
<!-- Font Awesome -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
|
|
<!-- Alpine.js -->
|
|
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
|
|
<!-- 인증 가드 -->
|
|
<script src="static/js/auth-guard.js"></script>
|
|
|
|
<!-- 공통 스타일 -->
|
|
<link rel="stylesheet" href="static/css/common.css">
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen" x-data="userManagementApp()">
|
|
<!-- 헤더 -->
|
|
<div id="header-container"></div>
|
|
|
|
<!-- 메인 컨테이너 -->
|
|
<div class="max-w-7xl mx-auto px-4 py-8">
|
|
<!-- 페이지 제목 -->
|
|
<div class="mb-8 flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">사용자 관리</h1>
|
|
<p class="text-gray-600">시스템 사용자를 관리하고 권한을 설정하세요.</p>
|
|
</div>
|
|
<button @click="showCreateModal = true" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
|
<i class="fas fa-plus mr-2"></i>새 사용자 추가
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 알림 메시지 -->
|
|
<div x-show="notification.show" x-transition class="mb-6 p-4 rounded-lg" :class="notification.type === 'success' ? 'bg-green-50 text-green-800 border border-green-200' : 'bg-red-50 text-red-800 border border-red-200'">
|
|
<div class="flex items-center">
|
|
<i :class="notification.type === 'success' ? 'fas fa-check-circle text-green-500' : 'fas fa-exclamation-circle text-red-500'" class="mr-2"></i>
|
|
<span x-text="notification.message"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 사용자 목록 -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-lg font-medium text-gray-900">사용자 목록</h2>
|
|
<div class="text-sm text-gray-500">
|
|
총 <span x-text="users.length"></span>명의 사용자
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 사용자 테이블 -->
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">사용자</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">역할</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">권한</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">가입일</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<template x-for="user in users" :key="user.id">
|
|
<tr>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-4">
|
|
<i class="fas fa-user text-blue-600"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900" x-text="user.full_name || user.email"></div>
|
|
<div class="text-sm text-gray-500" x-text="user.email"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
|
:class="user.role === 'root' ? 'bg-red-100 text-red-800' : user.role === 'admin' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'">
|
|
<i class="fas fa-crown mr-1" x-show="user.role === 'root'"></i>
|
|
<i class="fas fa-shield-alt mr-1" x-show="user.role === 'admin'"></i>
|
|
<i class="fas fa-user mr-1" x-show="user.role === 'user'"></i>
|
|
<span x-text="getRoleText(user.role)"></span>
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex space-x-1">
|
|
<span x-show="user.can_manage_books" class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 text-green-800">
|
|
<i class="fas fa-book mr-1"></i>서적
|
|
</span>
|
|
<span x-show="user.can_manage_notes" class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-blue-100 text-blue-800">
|
|
<i class="fas fa-sticky-note mr-1"></i>노트
|
|
</span>
|
|
<span x-show="user.can_manage_novels" class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-purple-100 text-purple-800">
|
|
<i class="fas fa-feather-alt mr-1"></i>소설
|
|
</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
|
:class="user.is_active ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'">
|
|
<i :class="user.is_active ? 'fas fa-check-circle' : 'fas fa-times-circle'" class="mr-1"></i>
|
|
<span x-text="user.is_active ? '활성' : '비활성'"></span>
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
<span x-text="formatDate(user.created_at)"></span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<div class="flex space-x-2">
|
|
<button @click="editUser(user)" class="text-blue-600 hover:text-blue-900">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button @click="confirmDeleteUser(user)"
|
|
x-show="user.role !== 'root'"
|
|
class="text-red-600 hover:text-red-900">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 사용자 생성 모달 -->
|
|
<div x-show="showCreateModal" x-transition class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
<div class="mt-3">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">새 사용자 추가</h3>
|
|
|
|
<form @submit.prevent="createUser()" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">이메일</label>
|
|
<input type="email" x-model="createForm.email" required
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">비밀번호</label>
|
|
<input type="password" x-model="createForm.password" required minlength="6"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">이름</label>
|
|
<input type="text" x-model="createForm.full_name"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">역할</label>
|
|
<select x-model="createForm.role"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="user">사용자</option>
|
|
<option value="admin" x-show="currentUser.role === 'root'">관리자</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">세션 타임아웃 (분)</label>
|
|
<select x-model="createForm.session_timeout_minutes"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="5">5분 (기본)</option>
|
|
<option value="15">15분</option>
|
|
<option value="30">30분</option>
|
|
<option value="60">1시간</option>
|
|
<option value="120">2시간</option>
|
|
<option value="480">8시간</option>
|
|
<option value="1440">24시간</option>
|
|
<option value="0">무제한</option>
|
|
</select>
|
|
<p class="mt-1 text-xs text-gray-500">0 = 무제한 (로그아웃 없음)</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">권한</label>
|
|
<div class="space-y-2">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="createForm.can_manage_books" class="mr-2">
|
|
<span class="text-sm">서적 관리</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="createForm.can_manage_notes" class="mr-2">
|
|
<span class="text-sm">노트 관리</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="createForm.can_manage_novels" class="mr-2">
|
|
<span class="text-sm">소설 관리</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3 pt-4">
|
|
<button type="button" @click="showCreateModal = false"
|
|
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50">
|
|
취소
|
|
</button>
|
|
<button type="submit" :disabled="createLoading"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50">
|
|
<i class="fas fa-spinner fa-spin mr-2" x-show="createLoading"></i>
|
|
<span x-text="createLoading ? '생성 중...' : '사용자 생성'"></span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 사용자 수정 모달 -->
|
|
<div x-show="showEditModal" x-transition class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
<div class="mt-3">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">사용자 수정</h3>
|
|
|
|
<form @submit.prevent="updateUser()" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">이메일</label>
|
|
<input type="email" x-model="editForm.email" disabled
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50 text-gray-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">이름</label>
|
|
<input type="text" x-model="editForm.full_name"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">역할</label>
|
|
<select x-model="editForm.role"
|
|
:disabled="editForm.role === 'root'"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50">
|
|
<option value="user">사용자</option>
|
|
<option value="admin" x-show="currentUser.role === 'root'">관리자</option>
|
|
<option value="root" x-show="editForm.role === 'root'">시스템 관리자</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="flex items-center mb-2">
|
|
<input type="checkbox" x-model="editForm.is_active" class="mr-2">
|
|
<span class="text-sm font-medium text-gray-700">계정 활성화</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">세션 타임아웃 (분)</label>
|
|
<select x-model="editForm.session_timeout_minutes"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="5">5분 (기본)</option>
|
|
<option value="15">15분</option>
|
|
<option value="30">30분</option>
|
|
<option value="60">1시간</option>
|
|
<option value="120">2시간</option>
|
|
<option value="480">8시간</option>
|
|
<option value="1440">24시간</option>
|
|
<option value="0">무제한</option>
|
|
</select>
|
|
<p class="mt-1 text-xs text-gray-500">0 = 무제한 (로그아웃 없음)</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">권한</label>
|
|
<div class="space-y-2">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="editForm.can_manage_books" class="mr-2">
|
|
<span class="text-sm">서적 관리</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="editForm.can_manage_notes" class="mr-2">
|
|
<span class="text-sm">노트 관리</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="editForm.can_manage_novels" class="mr-2">
|
|
<span class="text-sm">소설 관리</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3 pt-4">
|
|
<button type="button" @click="showEditModal = false"
|
|
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50">
|
|
취소
|
|
</button>
|
|
<button type="submit" :disabled="editLoading"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50">
|
|
<i class="fas fa-spinner fa-spin mr-2" x-show="editLoading"></i>
|
|
<span x-text="editLoading ? '수정 중...' : '사용자 수정'"></span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 삭제 확인 모달 -->
|
|
<div x-show="showDeleteModal" x-transition class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
<div class="mt-3 text-center">
|
|
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
|
|
<i class="fas fa-exclamation-triangle text-red-600"></i>
|
|
</div>
|
|
<h3 class="text-lg font-medium text-gray-900 mb-2">사용자 삭제</h3>
|
|
<p class="text-sm text-gray-500 mb-4">
|
|
<span x-text="deleteTarget?.full_name || deleteTarget?.email"></span> 사용자를 삭제하시겠습니까?<br>
|
|
이 작업은 되돌릴 수 없습니다.
|
|
</p>
|
|
|
|
<div class="flex justify-center space-x-3">
|
|
<button @click="showDeleteModal = false"
|
|
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50">
|
|
취소
|
|
</button>
|
|
<button @click="deleteUser()" :disabled="deleteLoading"
|
|
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 disabled:opacity-50">
|
|
<i class="fas fa-spinner fa-spin mr-2" x-show="deleteLoading"></i>
|
|
<span x-text="deleteLoading ? '삭제 중...' : '삭제'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 공통 스크립트 -->
|
|
<script src="static/js/api.js"></script>
|
|
<script src="static/js/header-loader.js"></script>
|
|
|
|
<!-- 사용자 관리 스크립트 -->
|
|
<script>
|
|
function userManagementApp() {
|
|
return {
|
|
users: [],
|
|
currentUser: {},
|
|
showCreateModal: false,
|
|
showEditModal: false,
|
|
showDeleteModal: false,
|
|
createLoading: false,
|
|
editLoading: false,
|
|
deleteLoading: false,
|
|
deleteTarget: null,
|
|
|
|
createForm: {
|
|
email: '',
|
|
password: '',
|
|
full_name: '',
|
|
role: 'user',
|
|
can_manage_books: true,
|
|
can_manage_notes: true,
|
|
can_manage_novels: true,
|
|
session_timeout_minutes: 5
|
|
},
|
|
|
|
editForm: {},
|
|
|
|
notification: {
|
|
show: false,
|
|
type: 'success',
|
|
message: ''
|
|
},
|
|
|
|
async init() {
|
|
console.log('🔧 사용자 관리 앱 초기화');
|
|
await this.loadCurrentUser();
|
|
await this.loadUsers();
|
|
},
|
|
|
|
async loadCurrentUser() {
|
|
try {
|
|
this.currentUser = await api.get('/users/me');
|
|
|
|
// 관리자 권한 확인
|
|
if (!this.currentUser.is_admin) {
|
|
window.location.href = 'index.html';
|
|
return;
|
|
}
|
|
|
|
// 헤더 사용자 메뉴 업데이트
|
|
if (window.updateUserMenu) {
|
|
window.updateUserMenu(this.currentUser);
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ 현재 사용자 정보 로드 실패:', error);
|
|
window.location.href = 'index.html';
|
|
}
|
|
},
|
|
|
|
async loadUsers() {
|
|
try {
|
|
this.users = await api.get('/users/');
|
|
console.log('✅ 사용자 목록 로드 완료:', this.users.length, '명');
|
|
} catch (error) {
|
|
console.error('❌ 사용자 목록 로드 실패:', error);
|
|
this.showNotification('사용자 목록을 불러올 수 없습니다.', 'error');
|
|
}
|
|
},
|
|
|
|
async createUser() {
|
|
this.createLoading = true;
|
|
try {
|
|
await api.post('/users/', this.createForm);
|
|
|
|
// 폼 초기화
|
|
this.createForm = {
|
|
email: '',
|
|
password: '',
|
|
full_name: '',
|
|
role: 'user',
|
|
can_manage_books: true,
|
|
can_manage_notes: true,
|
|
can_manage_novels: true,
|
|
session_timeout_minutes: 5
|
|
};
|
|
|
|
this.showCreateModal = false;
|
|
await this.loadUsers();
|
|
this.showNotification('새 사용자가 성공적으로 생성되었습니다.', 'success');
|
|
console.log('✅ 사용자 생성 완료');
|
|
} catch (error) {
|
|
console.error('❌ 사용자 생성 실패:', error);
|
|
this.showNotification('사용자 생성에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.createLoading = false;
|
|
}
|
|
},
|
|
|
|
editUser(user) {
|
|
this.editForm = { ...user };
|
|
this.showEditModal = true;
|
|
},
|
|
|
|
async updateUser() {
|
|
this.editLoading = true;
|
|
try {
|
|
await api.put(`/users/${this.editForm.id}`, {
|
|
full_name: this.editForm.full_name,
|
|
role: this.editForm.role,
|
|
is_active: this.editForm.is_active,
|
|
can_manage_books: this.editForm.can_manage_books,
|
|
can_manage_notes: this.editForm.can_manage_notes,
|
|
can_manage_novels: this.editForm.can_manage_novels,
|
|
session_timeout_minutes: this.editForm.session_timeout_minutes
|
|
});
|
|
|
|
this.showEditModal = false;
|
|
await this.loadUsers();
|
|
this.showNotification('사용자 정보가 성공적으로 수정되었습니다.', 'success');
|
|
console.log('✅ 사용자 수정 완료');
|
|
} catch (error) {
|
|
console.error('❌ 사용자 수정 실패:', error);
|
|
this.showNotification('사용자 수정에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.editLoading = false;
|
|
}
|
|
},
|
|
|
|
confirmDeleteUser(user) {
|
|
this.deleteTarget = user;
|
|
this.showDeleteModal = true;
|
|
},
|
|
|
|
async deleteUser() {
|
|
this.deleteLoading = true;
|
|
try {
|
|
await api.delete(`/users/${this.deleteTarget.id}`);
|
|
|
|
this.showDeleteModal = false;
|
|
this.deleteTarget = null;
|
|
await this.loadUsers();
|
|
this.showNotification('사용자가 성공적으로 삭제되었습니다.', 'success');
|
|
console.log('✅ 사용자 삭제 완료');
|
|
} catch (error) {
|
|
console.error('❌ 사용자 삭제 실패:', error);
|
|
this.showNotification('사용자 삭제에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.deleteLoading = false;
|
|
}
|
|
},
|
|
|
|
showNotification(message, type = 'success') {
|
|
this.notification = {
|
|
show: true,
|
|
type,
|
|
message
|
|
};
|
|
|
|
setTimeout(() => {
|
|
this.notification.show = false;
|
|
}, 5000);
|
|
},
|
|
|
|
getRoleText(role) {
|
|
const roleMap = {
|
|
'root': '시스템 관리자',
|
|
'admin': '관리자',
|
|
'user': '사용자'
|
|
};
|
|
return roleMap[role] || '사용자';
|
|
},
|
|
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString('ko-KR');
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|