sipsvc/app/src/main/assets/ritosip.html

1775 lines
70 KiB
HTML
Raw Normal View History

2025-08-06 17:36:15 +09:00
<!DOCTYPE html>
<html lang='ko'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>OSVC</title>
<link rel='stylesheet' href='./css/all.min.css'>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: Arial, sans-serif; }
body { display: flex; flex-direction: column; height: 100vh; background: #f4f4f4; }
.top-bar {
background-color: #5e5e60;
color: white;
padding: 28px;
text-align: left;
font-size: 30px;
font-weight: bold;
}
.container {
display: flex;
flex: 1;
}
.sidebar {
width: 250px;
background: #2c3e50;
color: white;
padding: 20px;
height: 100vh;
overflow-y: auto;
}
.sidebar h2 { font-size: 18px; margin-bottom: 20px; }
.sidebar ul { list-style: none; padding: 0; }
.sidebar ul li {
padding: 10px;
cursor: pointer;
transition: 0.3s;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar ul li:hover { background: #34495e; }
.sidebar ul li.active { color: #1abc9c; font-weight: bold; }
.submenu {
display: none;
padding-left: 20px;
border-left: 2px solid #1abc9c;
margin-top: 5px;
}
.submenu li {
padding: 8px;
font-size: 14px;
cursor: pointer;
background: #3b4c63;
margin-top: 2px;
}
.submenu li:hover { background: #1abc9c; }
.submenu li.active { color: #ffcc00; font-weight: bold; }
.content {
flex-grow: 1;
padding: 20px;
background: white;
}
.content-header {
font-size: 24px;
margin-bottom: 20px;
}
.content-header {
font-size: 24px;
margin-bottom: 20px;
}
.dashboard-container {
display: flex;
gap: 20px;
}
.dashboard-section {
flex: 1;
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.section-half {
width: 40vw;
}
.system-status ul {
list-style: none;
padding: 0;
}
.system-status ul li {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.status-indicator {
width: 15px;
height: 15px;
border-radius: 50%;
background: green;
margin-right: 10px;
}
.status-text {
margin-bottom: 5px;
}
.top-button {
width: 75px;
height: 75px;
background-color: #d32f2f; /* 빨간색 */
border-radius: 20%;
display: flex;
flex-direction: column; /* 세로 정렬 */
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: 0.3s;
border: none;
outline: none;
}
.top-button i {
font-size: 30px;
color: white;
}
.top-button-text {
font-size: 10px;
color: white;
margin-top: 8px; /* 아이콘과 간격 */
font-weight: bold;
}
.top-button:hover {
background-color: #b71c1c; /* 어두운 빨간색 */
}
.top-button-green {
width: 75px;
height: 75px;
background-color: #2fd32f; /* 초록색 */
border-radius: 20%;
display: flex;
flex-direction: column; /* 세로 정렬 */
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: 0.3s;
border: none;
outline: none;
}
.top-button-green i {
font-size: 30px;
color: white;
}
.top-button-green:hover {
background-color: #1cb71c; /* 어두운 초록색 */
}
.top-button-normal {
width: 75px;
height: 75px;
background-color: #3c4e60;
border-radius: 20%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: 0.3s;
border: none;
outline: none;
}
.top-button-normal i {
font-size: 30px;
color: white;
}
.top-button-normal:hover {
background-color: #1c2e40;
}
.top-info-normal {
width: 75px;
height: 75px;
background-color: #101010;
border-radius: 5%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
transition: 0.3s;
border: none;
outline: none;
}
.call-timer {
color: white;
font-size: 14px;
margin-bottom: 5px;
}
.time {
color: white;
font-size: 24px;
font-weight: bold;
}
.input-box {
margin-bottom: 15px;
display: flex;
}
.input-box input, .combo-box select {
width: 350px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
outline: none;
background: white;
}
.input-label {
vertical-align: middle;
margin-right: 10px;
line-height: 40px;
width:80px;
}
.input-label-long {
vertical-align: middle;
margin-right: 10px;
line-height: 40px;
width:120px;
}
.prev-label {
color: #808080;
vertical-align: middle;
margin-left: 5px;
margin-right: 10px;
line-height: 40px;
width:360px;
}
.apply-button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.apply-button:hover {
background-color: #45a049;
}
.table-inner-button {
padding: 5px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
transition: background 0.3s;
}
.table-inner-button:hover {
background-color: #409039;
}
.button-green {
background-color: #00ff00;
}
.button-orange {
background-color: #ffa500;
}
.button-orange:hover {
background-color: #ef9500;
}
.button-red {
background-color: #ff0000;
}
.button-red:hover {
background-color: #ef0000;
}
.table-container {
margin-top: 20px;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.15);
padding: 20px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 650px;
}
table {
width: 600px;
border-collapse: collapse;
border-radius: 10px;
overflow: hidden;
background: white;
}
thead {
background: #f4f4f4;
color: #333;
font-weight: bold;
border-bottom: 2px solid #ddd;
}
th, td {
padding: 10px;
text-align: center;
}
tbody tr:nth-child(even) {
background: #fafafa;
}
tbody tr:hover {
background: #f0f0f0;
transition: 0.3s;
}
th {
text-transform: none;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 30px;
gap: 5px;
}
.pagination button {
padding: 5px 10px;
border: 1px solid #ccc;
background-color: white;
cursor: pointer;
border-radius: 5px;
}
.pagination button:hover {
background-color: #ddd;
}
.pagination .active {
background-color: #ddd;
font-weight: bold;
}
.search-box {
margin-bottom: 10px;
width: 100%;
}
.search-box input {
width: 50%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
outline: none;
}
/* Dimmed 배경 */
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
/* 모달 스타일 */
.modal {
background: white;
padding: 20px;
border-radius: 8px;
width: 300px;
text-align: center;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
position: relative;
}
.modal h2 {
margin: 0 0 10px;
}
.modal button {
margin-top: 20px;
padding: 8px 16px;
border: none;
background: #007bff;
color: white;
cursor: pointer;
border-radius: 5px;
}
.modal button:hover {
background: #0056b3;
}
/* 로딩 스피너 스타일 */
.spinner {
margin: 20px auto;
width: 40px;
height: 40px;
border: 5px solid #ccc;
border-top: 5px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.toast {
visibility: hidden;
min-width: 250px;
min-height: 70px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 5px;
padding: 20px;
position: fixed;
/*
top: 20px;
left: 90%;
*/
font-size: 20px;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
opacity: 0;
transition: opacity 0.5s;
}
.toast.show {
visibility: visible;
opacity: 1;
}
.gallery {
display: flex;
gap: 10px;
}
.gallery img {
width: 100px;
height: 100px;
cursor: pointer;
border: 2px solid transparent;
transition: border 0.1s;
}
.gallery img.selected {
border: 4px solid blue !important;
}
#selectedImage {
margin-top: 20px;
font-weight: bold;
}
</style>
<script>
let seconds = 0;
let minutes = 0;
let hours = 0;
var g_call_status = 'standby';
var g_content_page = '';
var g_input_updated_page = '';
var callStartTime = null;
var callDuration = 0;
var timerId;
var timerReload;
var g_modal_confirm = '';
var g_confirm_callback = null;
function formatSeconds(seconds) {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
// 두 자리 숫자로 포맷 (padStart는 문자열 메서드)
/*
const formatted = [
hrs.toString().padStart(2, '0'),
mins.toString().padStart(2, '0'),
secs.toString().padStart(2, '0')
].join(':');
*/
const formatted =
(hrs.toString() + '시간 ' +
mins.toString() + '분 ' +
secs.toString() + '초');
return formatted;
}
function updateTimer() {
// seconds++;
// if (seconds === 60) {
// seconds = 0;
// minutes++;
// if (minutes === 60) {
// minutes = 0;
// hours++;
// }
// }
if(callStartTime != null) {
// 현재 시간 가져오기
const now = new Date();
// 밀리초 차이 계산
let diffMs = Math.abs(now - callStartTime);
// 시간, 분, 초로 변환
hours = Math.floor(diffMs / (1000 * 60 * 60));
diffMs %= (1000 * 60 * 60);
minutes = Math.floor(diffMs / (1000 * 60));
diffMs %= (1000 * 60);
seconds = Math.floor(diffMs / 1000);
}
if(callDuration > 0) {
let diffMs = callDuration * 1000;
hours = Math.floor(diffMs / (1000 * 60 * 60));
diffMs %= (1000 * 60 * 60);
minutes = Math.floor(diffMs / (1000 * 60));
diffMs %= (1000 * 60);
seconds = Math.floor(diffMs / 1000);
}
let formattedTime =
(hours < 10 ? '0' + hours : hours) + ':' +
(minutes < 10 ? '0' + minutes : minutes) + ':' +
(seconds < 10 ? '0' + seconds : seconds);
document.getElementById('timer').textContent = formattedTime;
fetchHTML('./sip_status', { cache: 'no-cache' }).then(html => {
//var poller = document.getElementById('status-poller');
//document.getElementById('sip-status').value = html;
var jsonString = html;
try {
// JSON 파싱
const data = JSON.parse(jsonString);
var prev_call_status = g_call_status;
g_call_status = data.call_status;
if(data.call_status == 'calling') {
if(modalOverlay.style.display == 'flex') {
modalOverlay.style.display = 'none';
}
if(data.audio_mute_status == 'mute') {
document.getElementById('area-button-mute').style.display = `none`;
document.getElementById('area-button-unmute').style.display = `flex`;
} else {
document.getElementById('area-button-mute').style.display = `flex`;
document.getElementById('area-button-unmute').style.display = `none`;
}
if(data.video_mute_status == 'mute') {
document.getElementById('area-button-video-mute').style.display = `none`;
document.getElementById('area-button-video-unmute').style.display = `flex`;
} else {
document.getElementById('area-button-video-mute').style.display = `flex`;
document.getElementById('area-button-video-unmute').style.display = `none`;
}
document.getElementById('area-call-progress').style = `visibility:visible`;
if(data.usb_mic_status != null) {
if(data.usb_mic_status == 'connected') {
if(data.audio_input_device == 'usb') {
document.getElementById('area-button-input-aux').style.display = `flex`;
document.getElementById('area-button-input-mic').style.display = `none`;
} else {
document.getElementById('area-button-input-aux').style.display = `none`;
document.getElementById('area-button-input-mic').style.display = `flex`;
}
} else {
document.getElementById('area-button-input-aux').style.display = `none`;
document.getElementById('area-button-input-mic').style.display = `none`;
}
}
if(data.far_screen_file != null) {
//console.log(data.far_screen_file);
var farImg = document.getElementById('img-far-screen');
if(farImg != null) {
farImg.src = './' + data.far_screen_file;
}
}
if(data.near_screen_file != null) {
//console.log(data.near_screen_file);
var nearImg = document.getElementById('img-near-screen');
if(nearImg != null) {
nearImg.src = './' + data.near_screen_file;
}
}
} else if(data.call_status == 'standby') {
document.getElementById('area-call-progress').style = `visibility:hidden`;
callStartTime = null;
if(data.near_screen_file != null && data.near_screen_file.length > 0) {
//console.log(data.near_screen_file);
var nearImg = document.getElementById('img-near-screen');
if(nearImg != null) {
nearImg.src = './' + data.near_screen_file;
}
}
}
if(data.call_start_time != null && callStartTime == null) {
callStartTime = new Date(data.call_start_time);
}
if(data.call_duration != null) {
callDuration = data.call_duration;
}
if(data.call_number != null) {
document.getElementById('text-call-number').innerHTML = data.call_number;
}
if(data.net_ip_address != null) {
document.getElementById('text-ip-address').innerHTML = data.net_ip_address;
}
if(data.sip_address != null) {
document.getElementById('text-sip-address').innerHTML = data.sip_address;
}
if(data.call_event_result != null) {
if(data.call_event_result == "call_failed") {
//fetchHTML('./request_action.html?req=property_reset&param=call_event_result');
showModalPopup("통화를 실패했습니다.");
}
}
if(g_content_page == 'dashboard') {
console.log(data);
if(data.server_register_status != null) {
if(data.server_register_status == 'registered') {
console.log('server_register_status : ' + data.server_register_status);
document.getElementById('server-register-status').className = 'status-indicator';
} else if(data.server_register_status == 'registering') {
console.log('server_register_status : ' + data.server_register_status);
document.getElementById('server-register-status').className = 'status-indicator button-orange';
} else {
document.getElementById('server-register-status').className = 'status-indicator button-red';
}
}
if(data.hdmi1_in_status != null) {
if(data.hdmi1_in_status == 'plugin') {
document.getElementById('hdmi1-in-status').className = 'status-indicator';
} else {
document.getElementById('hdmi1-in-status').className = 'status-indicator button-red';
}
}
if(data.hdmi2_in_status != null) {
if(data.hdmi2_in_status == 'plugin') {
document.getElementById('hdmi2-in-status').className = 'status-indicator';
} else {
document.getElementById('hdmi2-in-status').className = 'status-indicator button-red';
}
}
if(data.hdmi1_out_status != null) {
if(data.hdmi1_out_status == 'connected') {
document.getElementById('hdmi1-out-status').className = 'status-indicator';
} else {
document.getElementById('hdmi1-out-status').className = 'status-indicator button-red';
}
}
if(data.hdmi2_out_status != null) {
if(data.hdmi2_out_status == 'connected') {
document.getElementById('hdmi2-out-status').className = 'status-indicator';
} else {
document.getElementById('hdmi2-out-status').className = 'status-indicator button-red';
}
}
if(data.usb_mic_status != null) {
if(data.usb_mic_status == 'connected') {
document.getElementById('usb-mic-status').className = 'status-indicator';
} else {
document.getElementById('usb-mic-status').className = 'status-indicator button-red';
}
}
if(data.total_duration != null) {
document.getElementById('total-duration').innerHTML = formatSeconds(data.total_duration);
}
} else if(g_content_page == 'normalSetting') {
if(g_input_updated_page != g_content_page) {
g_input_updated_page = g_content_page;
document.getElementById('input-device-name').value = data.device_name;
document.getElementById('select-display-type').value = data.far_view_display_id;
}
/*
document.getElementById('prev-device-name').innerHTML = data.device_name;
if(data.far_view_display_id == '2') {
document.getElementById('prev-display-type').innerHTML = '모니터1:근거리/컨텐츠, 모니터2:원거리';
} else {
document.getElementById('prev-display-type').innerHTML = '모니터1:원거리, 모니터2:근거리/컨텐츠';
}
*/
//document.getElementById('prev-display-type').innerHTML = data.far_view_display_id;
} else if(g_content_page == 'networkSetting') {
if(g_input_updated_page != g_content_page) {
g_input_updated_page = g_content_page;
document.getElementById('select-network-type').value = data.ipv4_iptype;
onIpTypeSelectedValue(document.getElementById('select-network-type'));
document.getElementById('input-ipaddress').value = data.ipv4_address;
document.getElementById('input-netmask').value = data.ipv4_netmask;
document.getElementById('input-gateway').value = data.ipv4_gateway;
document.getElementById('input-dns').value = data.ipv4_dns;
}
/*
document.getElementById('prev-network-type').innerHTML = data.ipv4_iptype;
document.getElementById('prev-ipv4-address').innerHTML = data.ipv4_address;
document.getElementById('prev-ipv4-netmask').innerHTML = data.ipv4_netmask;
document.getElementById('prev-ipv4-gateway').innerHTML = data.ipv4_gateway;
document.getElementById('prev-ipv4-dns').innerHTML = data.ipv4_dns;
*/
} else if(g_content_page == 'serverSetting') {
if(g_input_updated_page != g_content_page) {
g_input_updated_page = g_content_page;
document.getElementById('input-display-name').value = data.display_name;
document.getElementById('input-account-name').value = data.account_name;
document.getElementById('select-media-encryption').value = data.media_encryption == 'SRTP' ? 'srtpm' : 'none';
document.getElementById('select-transport').value = data.prefer_transport;
document.getElementById('input-server-address').value = data.server_address;
}
/*
document.getElementById('prev-display-name').innerHTML = data.display_name;
document.getElementById('prev-account-name').innerHTML = data.account_name;
document.getElementById('prev-media-encryption').innerHTML = data.media_encryption;
document.getElementById('prev-prefer-transport').innerHTML = data.prefer_transport;
document.getElementById('prev-server-address').innerHTML = data.server_address;
*/
} else if(g_content_page == 'layout') {
if(g_input_updated_page != g_content_page) {
g_input_updated_page = g_content_page;
let layout_id = 'layout' + data.display_split_mode;
document.getElementById(layout_id).click();
}
document.getElementById('prev-layout').innerHTML = "적용중인 레이아웃: " + layoutAlt[data.display_split_mode - 1];
}
if(prev_call_status != g_call_status) {
if(g_content_page == 'monitoring') {
selectMenu('menu-monitoring');
} else if(g_content_page == 'callStatistics') {
selectMenu('menu-statistics');
}
if(g_call_status == "standby") {
console.log("통화가 종료되었습니다.");
showModalPopup("통화가 종료되었습니다.");
}
triggerUpdate();
if(g_content_page == "callHistory") {
setTimeout(() => {if(g_content_page == "callHistory") { callHistoryFromUrl(document.getElementById('menu-history')) }}, 200);
} else if(g_content_page == "callAddressBook") {
setTimeout(() => {if(g_content_page == "callAddressBook") { callAddressBookFromUrl(document.getElementById('menu-address')) }}, 200);
}
}
// 결과 표시
console.log(data.call_status);
} catch (error) {
console.log(error);
//alert("올바른 JSON 형식이 아닙니다!");
}
});
}
function triggerUpdate() {
fetchHTML('./trigger_update');
}
function showModalProgress() {
let modalOverlay = document.getElementById('modalOverlay');
let modalSpinner = document.getElementById('modalSpinner');
let modalMsg = document.getElementById('modalMsg');
modalMsg.innerHTML = '진행 중...';
modalSpinner.style.display = 'flex';
modalOverlay.style.display = 'flex';
}
function showModalPopup(msg) {
let modalOverlay = document.getElementById('modalOverlay');
let modalSpinner = document.getElementById('modalSpinner');
let modalMsg = document.getElementById('modalMsg');
modalMsg.innerHTML = msg;
modalSpinner.style.display = 'none';
modalOverlay.style.display = 'flex';
}
function showConfirmPopup(msg) {
let confirmOverlay = document.getElementById('confirmOverlay');
let confirmMsg = document.getElementById('confirmMsg');
confirmMsg.innerHTML = msg;
confirmOverlay.style.display = 'flex';
}
timerId = setInterval(updateTimer, 1000);
function startCallTimer() {
hours = 0;
minutes = 0;
seconds = 0;
updateTimer();
//let modalOverlay = document.getElementById('modalOverlay');
//modalOverlay.style.display = 'flex';
showModalProgress();
}
// *************************
// 테이블 관련 함수 영역
// *************************
function setupPagination(tableContainer) {
let currentPage = 1;
const rowsPerPage = 10;
const maxPageButtons = 5; // 최대 페이지 번호 개수
const table = tableContainer.querySelector('table');
const tbody = table.querySelector('tbody');
const rows = tbody.querySelectorAll('tr');
const paginationContainer = tableContainer.querySelector('.pagination');
const searchInput = tableContainer.querySelector(".search-box input");
function displayTableRows() {
if(searchInput) {
const searchText = searchInput.value.toLowerCase();
const filteredRows = Array.from(rows).filter(row =>
row.textContent.toLowerCase().includes(searchText)
);
const totalPages = Math.ceil(filteredRows.length / rowsPerPage);
rows.forEach(row => row.style.display = "none");
filteredRows.forEach((row, index) => {
row.style.display = (index >= (currentPage - 1) * rowsPerPage && index < currentPage * rowsPerPage) ? "table-row" : "none";
});
updatePaginationSearch(totalPages, filteredRows);
} else {
const totalPages = Math.ceil(rows.length / rowsPerPage);
rows.forEach((row, index) => {
row.style.display = (index >= (currentPage - 1) * rowsPerPage && index < currentPage * rowsPerPage) ? 'table-row' : 'none';
});
updatePagination(totalPages);
}
}
function changePageSearch(newPage, filteredRows) {
const totalPages = Math.ceil(filteredRows.length / rowsPerPage);
currentPage = Math.max(1, Math.min(newPage, totalPages));
displayTableRows();
}
function updatePaginationSearch(totalPages, filteredRows) {
paginationContainer.innerHTML = "";
let startPage = Math.max(1, currentPage - Math.floor(maxPageButtons / 2));
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
if (endPage - startPage + 1 < maxPageButtons) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
let prevButton = document.createElement("button");
prevButton.innerText = "이전";
prevButton.disabled = currentPage === 1;
prevButton.onclick = () => changePageSearch(currentPage - 1, filteredRows);
paginationContainer.appendChild(prevButton);
for (let i = startPage; i <= endPage; i++) {
let pageButton = document.createElement("button");
pageButton.innerText = i;
if (i === currentPage) {
pageButton.classList.add("active");
}
pageButton.onclick = () => changePageSearch(i, filteredRows);
paginationContainer.appendChild(pageButton);
}
let nextButton = document.createElement("button");
nextButton.innerText = "다음";
nextButton.disabled = currentPage === totalPages;
nextButton.onclick = () => changePageSearch(currentPage + 1, filteredRows);
paginationContainer.appendChild(nextButton);
}
function changePage(newPage) {
const totalPages = Math.ceil(rows.length / rowsPerPage);
currentPage = Math.max(1, Math.min(newPage, totalPages));
displayTableRows();
}
function updatePagination(totalPages) {
paginationContainer.innerHTML = "";
let startPage = Math.max(1, currentPage - Math.floor(maxPageButtons / 2));
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
if (endPage - startPage + 1 < maxPageButtons) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
let prevButton = document.createElement("button");
prevButton.innerText = "이전";
prevButton.disabled = currentPage === 1;
prevButton.onclick = () => changePage(currentPage - 1);
paginationContainer.appendChild(prevButton);
if (startPage > 1) {
let firstPage = document.createElement("button");
firstPage.innerText = "1";
firstPage.onclick = () => changePage(1);
paginationContainer.appendChild(firstPage);
if (startPage > 2) {
let dots = document.createElement("span");
dots.innerText = "...";
paginationContainer.appendChild(dots);
}
}
for (let i = startPage; i <= endPage; i++) {
let pageButton = document.createElement("button");
pageButton.innerText = i;
if (i === currentPage) {
pageButton.classList.add("active");
}
pageButton.onclick = () => changePage(i);
paginationContainer.appendChild(pageButton);
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
let dots = document.createElement("span");
dots.innerText = "...";
paginationContainer.appendChild(dots);
}
let lastPage = document.createElement("button");
lastPage.innerText = totalPages;
lastPage.onclick = () => changePage(totalPages);
paginationContainer.appendChild(lastPage);
}
let nextButton = document.createElement("button");
nextButton.innerText = "다음";
nextButton.disabled = currentPage === totalPages;
nextButton.onclick = () => changePage(currentPage + 1);
paginationContainer.appendChild(nextButton);
}
if(searchInput) {
searchInput.addEventListener("input", displayTableRows);
}
displayTableRows();
}
/*
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.table-container').forEach(setupPagination);
});
*/
function updateTablePager() {
document.querySelectorAll('.table-container').forEach(setupPagination);
}
async function fetchHTML(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const html = await response.text();
return html;
} catch (error) {
console.error('Error fetching HTML:', error);
return null;
}
}
// *************************
// 좌측 메뉴 관련 함수 영역
// *************************
function toggleSubMenu(id) {
let submenu = document.getElementById(id);
let icon = document.getElementById(id + '-icon');
if (submenu) {
if (submenu.style.display === 'block') {
submenu.style.display = 'none';
icon?.classList.remove('fa-chevron-down');
icon?.classList.add('fa-chevron-right');
} else {
submenu.style.display = 'block';
icon?.classList.remove('fa-chevron-right');
icon?.classList.add('fa-chevron-down');
}
}
}
function openSubMenu(id) {
let submenu = document.getElementById(id);
let icon = document.getElementById(id + '-icon');
if (submenu) {
if (submenu.style.display !== 'block') {
submenu.style.display = 'block';
icon?.classList.remove('fa-chevron-right');
icon?.classList.add('fa-chevron-down');
}
}
}
function loadContent(content, element) {
document.getElementById('content-area').innerHTML = content;
document.querySelectorAll('.sidebar ul li, .submenu li').forEach(item => item.classList.remove('active'));
element.classList.add('active');
}
function changeContentHeader(headerInfo) {
document.getElementById('content-header').innerHTML = headerInfo;
}
function requestMute() {
fetchHTML('./request_action.html?req=audio_mute');
setTimeout(updateTimer, 100);
}
function requestUnMute() {
fetchHTML('./request_action.html?req=audio_unmute');
setTimeout(updateTimer, 100);
}
function requestVideoMute() {
fetchHTML('./request_action.html?req=video_mute');
setTimeout(updateTimer, 100);
}
function requestVideoUnMute() {
fetchHTML('./request_action.html?req=video_unmute');
setTimeout(updateTimer, 100);
}
function callStop() {
fetchHTML('./request_action.html?req=stop_call');
}
function setAudioSource(target) {
fetchHTML('./request_action.html?req=audio_source&target=' + target);
}
function callStart() {
var callNumber = document.getElementById('input-call-number').value;
if(callNumber.length > 0) {
callStartWithNumber(callNumber);
}
}
function saveAddressBook() {
var uri = document.getElementById('input-uri').value;
var name = document.getElementById('input-name').value;
var paramStr = '&uri=' + uri;
paramStr += '&name=' + name;
fetchHTML('./request_action.html?req=save_address&'+paramStr);
triggerUpdate();
setTimeout(() => {if(g_content_page == "callAddressBook") { callAddressBookFromUrl(document.getElementById('menu-address')) }}, 200);
}
function removeContact(peerUri) {
fetchHTML('./request_action.html?req=remove_address&uri='+peerUri);
triggerUpdate();
setTimeout(() => {if(g_content_page == "callAddressBook") { callAddressBookFromUrl(document.getElementById('menu-address')) }}, 200);
}
function removeHistoryConfirm() {
fetchHTML('./request_action.html?req=remove_history');
triggerUpdate();
setTimeout(() => {if(g_content_page == "callHistory") { callHistoryFromUrl(document.getElementById('menu-history')) }}, 200);
showToast("삭제 되었습니다.");
}
function removeHistory() {
g_confirm_callback = removeHistoryConfirm;
showConfirmPopup('삭제하시겠습니까?');
}
function callStartWithNumber(callNumber) {
fetchHTML('./request_action.html?req=start_call&param='+callNumber);
startCallTimer();
document.getElementById('text-call-number').innerHTML = callNumber;
//document.getElementById('area-call-progress').style = `visibility:visible`;
}
function onIpTypeSelectedValue(selectElement) {
if(selectElement.options[selectElement.selectedIndex].text == 'DHCP') {
document.getElementById('area-static-ipssetting').style = `display:none`;
} else if(selectElement.options[selectElement.selectedIndex].text == 'STATIC') {
document.getElementById('area-static-ipssetting').style = `display:block`;
}
}
function applyNetwork() {
var netType = document.getElementById('select-network-type').value;
if(netType == 'dhcp') {
fetchHTML('./request_action.html?req=network_setting&type=dhcp');
showToast("적용 되었습니다");
} else if(netType == 'static') {
var ipAddress = document.getElementById('input-ipaddress').value;
var netmask = document.getElementById('input-netmask').value;
var gateway = document.getElementById('input-gateway').value;
var dns = document.getElementById('input-dns').value;
var paramStr = '&address=' + ipAddress;
paramStr += '&netmask=' + netmask;
paramStr += '&gateway=' + gateway;
paramStr += '&dns=' + dns;
fetchHTML('./request_action.html?req=network_setting&type=static' + paramStr);
showToast("적용 되었습니다");
}
//console.log('네트워크 타입 : ' + netType);
}
function applyServerSetting() {
var displayName = document.getElementById('input-display-name').value;
var accountName = document.getElementById('input-account-name').value;
var password = document.getElementById('input-account-password').value;
var serverAddr = document.getElementById('input-server-address').value;
var transport = document.getElementById('select-transport').value;
var mediaEncryption = document.getElementById('select-media-encryption').value;
var paramStr = '&display_name=' + displayName;
paramStr += '&account_name=' + accountName;
paramStr += '&password=' + password;
paramStr += '&server_address=' + serverAddr;
paramStr += '&transport=' + transport;
paramStr += '&media_encryption=' + mediaEncryption;
fetchHTML('./request_action.html?req=server_setting' + paramStr);
showToast("적용 되었습니다");
}
function applyLayoutSetting() {
var layout = document.getElementById('layout-selection').innerText;
fetchHTML('./request_action.html?req=layout_setting&layout=' + layout);
showToast("적용 되었습니다");
}
function applyMiscSetting() {
var deviceName = document.getElementById('input-device-name').value;
var displayType = document.getElementById('select-display-type').value;
var paramStr = '&device_name=' + deviceName;
paramStr += '&display_type=' + displayType;
fetchHTML('./request_action.html?req=misc_setting' + paramStr);
showToast("적용 되었습니다");
}
// *************************
// 메뉴별 컨텐츠 관련 함수 영역
// *************************
function callSendContent() {
g_content_page = "callSend";
pageChanged();
var source = `\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:10px'>전화 걸기</h3>\
<div class='input-box'>\
<input id='input-call-number' type='text' placeholder='전화번호'>\
</div>\
<div>\
<button class='top-button-green' onclick='callStart()'>\
<i class='fa-solid fa-phone-volume'></i>\
<div class='top-button-text'>전화걸기</div>\
</button>\
</div>\
</div>\
`;
return source;
}
function callAddressBookContent() {
var source = `\
<div class='dashboard-container'>\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:10px'>주소록</h3>\
<div class='table-container'>\
<div class="search-box">\
<input type="text" placeholder="검색...">\
</div>\
<table>\
<thead>\
<tr>\
<th width='30%'>연결번호</th>\
<th width='30%'>이름</th>\
<th>제어</th>\
</tr>\
</thead>\
<tbody>\
<tr><td>7005</td><td>김가나</td><td><button class='table-inner-button' onclick='#'>통화</button><button class='table-inner-button button-orange' style='margin-left:3px' onclick='#'>수정</button><button class='table-inner-button button-red' style='margin-left:3px' onclick='#'>삭제</button></td></tr>\
<tr><td>7005</td><td>김나다</td><td><button class='table-inner-button' onclick='#'>통화</button><button class='table-inner-button button-orange' style='margin-left:3px' onclick='#'>수정</button><button class='table-inner-button button-red' style='margin-left:3px' onclick='#'>삭제</button></td></tr>\
<tr><td>7005</td><td>김다라</td><td><button class='table-inner-button' onclick='#'>통화</button><button class='table-inner-button button-orange' style='margin-left:3px' onclick='#'>수정</button><button class='table-inner-button button-red' style='margin-left:3px' onclick='#'>삭제</button></td></tr>\
<tr><td>7002</td><td>김라마</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7001</td><td>이가나</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7005</td><td>이나다</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7005</td><td>이다라</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7005</td><td>박가나</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7002</td><td>박나다</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7001</td><td>박다라</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7005</td><td>최가나</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
<tr><td>7005</td><td>최나다</td><td><button class='table-inner-button' onclick='#'>통화</button></td></tr>\
</tbody>\
</table>\
<div class='pagination'></div>\
</div>\
</div>\
<div class='dashboard-section'>\
<h3 style='margin-bottom:10px'></h3>\
<p><strong>Device Name:</strong> StudioX52-84B2ECFM</p>\
<p><strong>Software Version:</strong> 4.4.1-426075</p>\
<p><strong>MAC Address:</strong> 00:e0:db:84:b2:ec</p>\
</div>\
</div>`;
return source;
}
function callAddressBookFromUrl(element) {
g_content_page = "callAddressBook";
pageChanged();
fetchHTML('./call_address').then(html => {
loadContent(html, element);
updateTablePager();
});
}
function callHistoryFromUrl(element) {
g_content_page = "callHistory";
pageChanged();
fetchHTML('./call_history').then(html => {
loadContent(html, element);
updateTablePager();
});
}
function dashboardFromUrl(element) {
g_content_page = "dashboard";
pageChanged();
fetchHTML('./dashboard').then(html => {
loadContent(html, element);
});
}
function callStatisticsFromUrl(element) {
g_content_page = "callStatistics";
pageChanged();
callStatisticsFromUrlProxy(element);
}
function callStatisticsFromUrlProxy(element) {
if(g_call_status != 'calling') {
loadContent("<h3 style='margin-bottom:10px'>현재 통화중이 아닙니다.</h3>", element);
return;
}
fetchHTML('./call_statistics').then(html => {
loadContent(html, element);
});
if(g_content_page == "callStatistics") {
timerReload = setTimeout(() => {callStatisticsFromUrlProxy(element)}, 1000);
}
}
function pageChanged() {
clearTimeout(timerReload);
g_input_updated_page = '';
}
function monitoringContent() {
g_content_page = "monitoring";
pageChanged();
if(g_call_status != 'calling') {
var source = `\
<h3 style='margin-bottom:10px'>현재 통화중이 아닙니다.</h3>\
<div class='dashboard-container' style='width:70vw;'>\
<div class='dashboard-section'>\
<h3 style='margin-bottom:10px'>근거리 화면</h3>\
<img id='img-near-screen' src='' style='width:100%'>\
</div>\
</div>`;
return source;
//return "<h3 style='margin-bottom:10px'>현재 통화중이 아닙니다.</h3>";
}
var source = `\
<div class='dashboard-container'>\
<div class='dashboard-section section-half'>\
<h3 style='margin-bottom:10px'>원거리 화면</h3>\
<img id='img-far-screen' src='' style='width:100%'>\
</div>\
<div class='dashboard-section section-half'>\
<h3 style='margin-bottom:10px'>근거리 화면</h3>\
<img id='img-near-screen' src='' style='width:100%'>\
</div>\
</div>`;
return source;
}
function miscSettingContent() {
g_content_page = "normalSetting";
setTimeout(updateTimer, 1);
pageChanged();
var source = `\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:20px'>일반 설정</h3>\
<div style='display: flex;'>\
<label class='input-label-long'>장치 이름</label>\
<div class='input-box'>\
<input id='input-device-name' type='text'>\
</div>\
<label class='prev-label' id='prev-device-name'></label>\
</div>\
<div style='display: flex;margin-bottom: 20px'>\
<label class='input-label-long'>모니터 설정</label>\
<div class='combo-box'>\
<select id='select-display-type'>\
<option value=''></option>\
<option value='1'>모니터1:원거리, 모니터2:근거리/컨텐츠</option>\
<option value='2'>모니터1:근거리/컨텐츠, 모니터2:원거리</option>\
</select>\
</div>\
<label class='prev-label' id='prev-display-type'></label>\
</div>\
<button class='apply-button' onclick='applyMiscSetting()'>적용</button>\
</div>\
`;
return source;
}
function networkSettingContent() {
g_content_page = "networkSetting";
pageChanged();
setTimeout(updateTimer, 1);
var source = `\
<div class='dashboard-container'>\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:20px'>네트워크 설정</h3>\
<div style='display: flex;margin-bottom: 20px'>\
<label class='input-label'>IP할당방식</label>\
<div class='combo-box'>\
<select id='select-network-type' onchange='onIpTypeSelectedValue(this)'>\
<option value=''></option>\
<option value='dhcp'>DHCP</option>\
<option value='static'>STATIC</option>\
</select>\
</div>\
<label class='prev-label' id='prev-network-type'></label>\
</div>\
<span id='area-static-ipssetting' style='display:none'>\
<div style='display: flex;'>\
<label class='input-label'>IP</label>\
<div class='input-box'>\
<input id='input-ipaddress' type='text'>\
</div>\
<label class='prev-label' id='prev-ipv4-address'></label>\
</div>\
<div style='display: flex;'>\
<label class='input-label'>NETMASK</label>\
<div class='input-box'>\
<input id='input-netmask' type='text'>\
</div>\
<label class='prev-label' id='prev-ipv4-netmask'></label>\
</div>\
<div style='display: flex;'>\
<label class='input-label'>GATEWAY</label>\
<div class='input-box'>\
<input id='input-gateway' type='text'>\
</div>\
<label class='prev-label' id='prev-ipv4-gateway'></label>\
</div>\
<div style='display: flex;'>\
<label class='input-label'>DNS</label>\
<div class='input-box'>\
<input id='input-dns' type='text'>\
</div>\
<label class='prev-label' id='prev-ipv4-dns'></label>\
</div>\
</span>\
<button class='apply-button' onclick='applyNetwork()'>적용</button>\
</div>\
</div>`;
return source;
}
function serverSettingContent() {
g_content_page = "serverSetting";
pageChanged();
setTimeout(updateTimer, 1);
var source = `\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:20px'>SIP 설정</h3>\
<div style='display: flex;'>\
<label class='input-label' style='width:120px'>표시명</label>\
<div class='input-box'>\
<input id='input-display-name' type='text'>\
</div>\
<label class='prev-label' id='prev-display-name'></label>\
</div>\
<div style='display: flex;'>\
<label class='input-label' style='width:120px'>인증 사용자명</label>\
<div class='input-box'>\
<input id='input-account-name' type='text'>\
</div>\
<label class='prev-label' id='prev-account-name'></label>\
</div>\
<div style='display: flex;'>\
<label class='input-label' style='width:120px'>인증 암호</label>\
<div class='input-box'>\
<input id='input-account-password' type='text'>\
</div>\
</div>\
<div style='display: flex;'>\
<label class='input-label' style='width:120px'>SIP 서버주소</label>\
<div class='input-box'>\
<input id='input-server-address' type='text'>\
</div>\
<label class='prev-label' id='prev-server-address'></label>\
</div>\
<div style='display: flex;margin-bottom: 15px'>\
<label class='input-label' style='width:120px'>트랜스포트</label>\
<div class='combo-box'>\
<select id='select-transport'>\
<option value='udp'>UDP</option>\
<option value='tcp'>TCP</option>\
<option value='tls'>TLS</option>\
</select>\
</div>\
<label class='prev-label' id='prev-prefer-transport'></label>\
</div>\
<div style='display: flex;margin-bottom: 15px'>\
<label class='input-label' style='width:120px'>미디어 암호화</label>\
<div class='combo-box'>\
<select id='select-media-encryption'>\
<option value='none'>사용안함</option>\
<!--option value='srtpo'>SRTP 옵션</option-->\
<option value='srtpm'>SRTP</option>\
</select>\
</div>\
<label class='prev-label' id='prev-media-encryption'></label>\
</div>\
<button class='apply-button' onclick='applyServerSetting()' style='margin-top:15px'>적용</button>\
</div>\
`;
return source;
}
var layoutAlt = [
"원거리 전체화면, 근거리 우하단",
"원거리 전체화면, 근거리 우상단",
"원거리 전체화면, 근거리 좌상단",
"원거리 전체화면, 근거리 좌하단",
"원거리 대상단, 근거리 소하단",
"원거리 대좌단, 근거리 소우단",
"원거리 대하단, 근거리 소상단",
"원거리 반좌단, 근거리 반우단",
"원거리 전체화면, 근거리 없음",
"원거리 없음, 근거리 전체화면"
];
function layoutContent() {
g_content_page = "layout";
pageChanged();
setTimeout(updateTimer, 1);
var source = `\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:20px'>PIP 레이아웃 설정</h3>\
<div class="gallery" id="imageGallery">\
<img id='layout1' src="layout1.png" alt="원거리 전체화면, 근거리 우하단" onclick="selectImage(this)">\
<img id='layout2' src="layout2.png" alt="원거리 전체화면, 근거리 우상단" onclick="selectImage(this)">\
<img id='layout3' src="layout3.png" alt="원거리 전체화면, 근거리 좌상단" onclick="selectImage(this)">\
<img id='layout4' src="layout4.png" alt="원거리 전체화면, 근거리 좌하단" onclick="selectImage(this)">\
<img id='layout5' src="layout5.png" alt="원거리 대상단, 근거리 소하단" onclick="selectImage(this)">\
<img id='layout6' src="layout6.png" alt="원거리 대좌단, 근거리 소우단" onclick="selectImage(this)">\
<img id='layout7' src="layout7.png" alt="원거리 대하단, 근거리 소상단" onclick="selectImage(this)">\
<img id='layout8' src="layout8.png" alt="원거리 반좌단, 근거리 반우단" onclick="selectImage(this)">\
<img id='layout9' src="layout9.png" alt="원거리 전체화면, 근거리 없음" onclick="selectImage(this)">\
<img id='layout10' src="layout10.png" alt="원거리 없음, 근거리 전체화면" onclick="selectImage(this)">\
</div>\
<div id="selectedImage">선택된 레이아웃: 없음</div> \
<div id="prev-layout" style="margin-top:10px; padding-top:10px; padding-bottom:10px; font-weight:bold; background-color:#c0c0c0">&nbsp;</div> \
<span id="layout-selection" style="display:none"></span> \
<button class='apply-button' onclick='applyLayoutSetting()' style='margin-top:15px'>적용</button>\
</div>\
`;
return source;
}
function dashboardContent() {
g_content_page = "dashboard";
pageChanged();
var source = `<div class='dashboard-container'>\
<div class='dashboard-section system-status'>\
<h3 style='margin-bottom:10px'>시스템 상태</h3>\
<ul>\
<li><span class='status-indicator'></span>네트워크 연결</li>\
<li><span id='hdmi1-in-status' class='status-indicator button-orange'></span>HDMI 입력 1</li>\
<li><span id='hdmi2-in-status' class='status-indicator button-orange'></span>HDMI 입력 2</li>\
<li><span id='hdmi1-out-status' class='status-indicator button-orange'></span>HDMI 출력 1</li>\
<li><span id='hdmi2-out-status' class='status-indicator button-orange'></span>HDMI 출력 2</li>\
<li><span id='usb-mic-status' class='status-indicator button-orange'></span>USB 마이크 연결</li>\
<li><span id='server-register-status' class='status-indicator button-orange'></span>SIP서버 연결</li>\
</ul>\
</div>\
<div class='dashboard-section'>\
<h3 style='margin-bottom:10px'>시스템 정보</h3>\
<p class='status-text'><strong>Device Name:</strong> OSVC-C220</p>\
<p class='status-text'><strong>Software Version:</strong> v1.0.0</p>\
<p class='status-text'><strong>MAC Address:</strong> ----</p>\
</div>\
</div>\
</div>`;
return source;
}
function selectMenu(menuId) {
var item = document.getElementById(menuId);
if(item != null) {
if(menuId == 'menu-network') {
openSubMenu('settings');
} else if(menuId == 'menu-monitoring' || menuId == 'menu-statistics') {
openSubMenu('call-monitoring');
}
item.click();
}
}
function selectImage(imgElement) {
// 모든 이미지의 선택 스타일 제거
document.querySelectorAll('.gallery img').forEach(img => {
img.classList.remove('selected');
});
// 선택한 이미지에 클래스 추가
imgElement.classList.add('selected');
const match = imgElement.src.match(/\/([\w-]+)\.png$/);
document.getElementById('layout-selection').innerText = match[1];
// 선택된 이미지 정보 표시
document.getElementById('selectedImage').innerText = `선택된 레이아웃: ${imgElement.alt}`;
}
</script>
</head>
<body onload="selectMenu('menu-dashboard');triggerUpdate();updateTimer()">
<iframe name='runner' id='runner' style="position:absolute;left:0px;top:0px;width:1px;height:1px;visibility:hidden;"></iframe>
<!-- <iframe id='status-poller' style="position:absolute;left:0px;top:0px;width:1px;height:1px;visibility:hidden;">
<input id="sip-status" type="text">
</iframe> -->
<input id="sip-status" type="text" style="position:absolute;left:0px;top:0px;width:1px;height:1px;visibility:hidden;">
<div class='top-bar'>OSVC</div>
<span id='area-call-progress' style='visibility:hidden'>
<span class='top-info-normal' style='position:absolute;top:7px;left:250px;width:120px'>
<div class='call-timer'>통화 번호</div>
<div id='text-call-number' class='time'>7003</div>
</span>
<span class='top-info-normal' style='position:absolute;top:7px;left:380px;width:140px'>
<div class='call-timer'>통화 시간</div>
<div class='time' id='timer'>00:00:00</div>
</span>
<div id='area-button-video-unmute' style='position:absolute;top:7px;left:600px'>
<button class='top-button' onclick="requestVideoUnMute()">
<i class='fas fa-video'></i>
<div class='top-button-text'>영상 켜기</div>
</button>
</div>
<div id='area-button-video-mute' style='position:absolute;top:7px;left:600px'>
<button class='top-button-green' onclick="requestVideoMute()">
<i class='fas fa-video-slash'></i>
<div class='top-button-text'>영상 끄기</div>
</button>
</div>
<div id='area-button-unmute' style='position:absolute;top:7px;left:700px'>
<button class='top-button' onclick="requestUnMute()">
<i class='fas fa-microphone'></i>
<div class='top-button-text'>음소거 해제</div>
</button>
</div>
<div id='area-button-mute' style='position:absolute;top:7px;left:700px'>
<button class='top-button-green' onclick="requestMute()">
<i class='fas fa-microphone'></i>
<div class='top-button-text'>음소거</div>
</button>
</div>
<div style='position:absolute;top:7px;left:800px'>
<button class='top-button-normal' onclick="selectMenu('menu-statistics')">
<i class='fa-solid fa-signal'></i>
<div class='top-button-text'>통화 통계</div>
</button>
</div>
<div style='position:absolute;top:7px;left:900px'>
<button class='top-button-normal' onclick="selectMenu('menu-monitoring')">
<i class='fa-solid fa-computer'></i>
<div class='top-button-text'>모니터링</div>
</button>
</div>
<div style='position:absolute;top:7px;left:1000px'>
<button class='top-button' onclick='callStop()'>
<i class='fa-solid fa-phone-slash'></i>
<div class='top-button-text'>전화 끊기</div>
</button>
</div>
<div id='area-button-input-aux' style='position:absolute;top:7px;left:1100px'>
<button class='top-button-normal' onclick='setAudioSource("aux")'>
<i class='fa-solid fa-circle-play'></i>
<div class='top-button-text'>AUX 입력</div>
</button>
</div>
<div id='area-button-input-mic' style='position:absolute;top:7px;left:1100px'>
<button class='top-button-green' onclick='setAudioSource("mic")'>
<i class='fa-solid fa-microphone-lines'></i>
<div class='top-button-text'>MIC 입력</div>
</button>
</div>
</span>
<div style='position:absolute;top:7px;left:1300px'>
<div style='display:flex;margin-top:15px;'>
<div style='font-size:13px;color:white;margin-right:5px;width:55px;text-align:right'>IP주소:</div>
<div id='text-ip-address' style='font-size:15px;color:white'></div>
</div>
<div style='display:flex;margin-top:10px;'>
<div style='font-size:13px;color:white;margin-right:5px;width:55px;text-align:right'>SIP주소:</div>
<div id='text-sip-address' style='font-size:15px;color:white'></div>
</div>
</div>
<div class='container'>
<div class='sidebar'>
<h2>OSVC-C220</h2>
<ul>
<!-- <li id="menu-dashboard" onclick='loadContent(dashboardContent(), this)' class='active'>대시보드</li>-->
<li id="menu-dashboard" onclick='dashboardFromUrl(this)' class='active'>대시보드</li>
<li onclick='toggleSubMenu(`call-management`)'>통화 관리 <i id='call-management-icon' class='fas fa-chevron-right'></i></li>
<ul class='submenu' id='call-management'>
<!--<li onclick='loadContent(`<h3>전화 걸기</h3><p>통화 기록을 확인하세요.</p>`, this)'>전화 걸기</li>-->
<li onclick='loadContent(callSendContent(), this)'>전화 걸기</li>
<li id="menu-address" onclick='callAddressBookFromUrl(this)'>주소록</li>
<!--<li onclick='loadContent(callHistoryContent(), this);updateTablePager();'>최근 통화</li>-->
<li id="menu-history" onclick='callHistoryFromUrl(this)'>최근 통화</li>
</ul>
<li onclick='toggleSubMenu(`call-monitoring`)'>통화 상태 <i id='call-monitoring-icon' class='fas fa-chevron-right'></i></li>
<ul class='submenu' id='call-monitoring'>
<li id="menu-monitoring" onclick='loadContent(monitoringContent(), this)'>모니터링</li>
<li id="menu-statistics" onclick='callStatisticsFromUrl(this)'>통화통계</li>
</ul>
<li onclick='toggleSubMenu(`settings`)'>설정 <i id='settings-icon' class='fas fa-chevron-right'></i></li>
<ul class='submenu' id='settings'>
<li onclick='loadContent(miscSettingContent(), this);'>일반 설정</li>
<li id="menu-network" onclick='loadContent(networkSettingContent(), this)'>네트워크 설정</li>
<li onclick='loadContent(serverSettingContent(), this)'>SIP 설정</li>
<li onclick='loadContent(layoutContent(), this)'>화면 설정</li>
</ul>
</ul>
</div>
<div class='content'>
<!--<div id='content-header' class='content-header'>대시보드 - 시스템 상태</div>-->
<div id='content-area'>
</div>
</div>
</div>
</div>
<!-- 모달 배경 (dimmed) -->
<div class="modal-overlay" id="modalOverlay">
<!-- 모달 내용 -->
<div class="modal">
<h2 id="modalMsg" >진행 중...</h2>
<div id="modalSpinner" class="spinner"></div>
<button id="closeModalBtn">닫기</button>
</div>
</div>
<div class="modal-overlay" id="confirmOverlay">
<!-- 모달 내용 -->
<div class="modal">
<h2 id="confirmMsg" >진행 중...</h2>
<button id="yesModalBtn"></button>
<button id="noModalBtn">아니오</button>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
const closeModalBtn = document.getElementById('closeModalBtn');
const yesModalBtn = document.getElementById('yesModalBtn');
const noModalBtn = document.getElementById('noModalBtn');
// 모달 닫기 (버튼 클릭)
closeModalBtn.addEventListener('click', () => {
modalOverlay.style.display = 'none';
});
yesModalBtn.addEventListener('click', () => {
g_modal_confirm = 'y';
confirmOverlay.style.display = 'none';
if(g_confirm_callback != null) {
g_confirm_callback();
}
});
noModalBtn.addEventListener('click', () => {
g_modal_confirm = 'n';
confirmOverlay.style.display = 'none';
});
function showToast(message) {
let toast = document.getElementById("toast");
toast.innerText = message;
toast.classList.add("show");
setTimeout(() => {
toast.classList.remove("show");
}, 3000);
}
</script>
</body>
</html>