633 lines
27 KiB
PHP
633 lines
27 KiB
PHP
<?php
|
|
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
|
require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/upload_admin_guard.php';
|
|
|
|
// 로그인 안 되었거나 관리자 아니면 바로 로그인 화면으로
|
|
if (empty($_SESSION['user']['userId']) || !is_upload_admin()) {
|
|
header("Location: /egbim/index.php?popup=login");
|
|
exit;
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>배포 파일 관리 시스템</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
</head>
|
|
<body class="bg-gray-100 p-6">
|
|
<div class="max-w-screen-xl mx-auto">
|
|
<div class="flex items-center mb-4">
|
|
<svg class="w-7 h-7 text-blue-600 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5-5m0 0l5 5m-5-5v12" />
|
|
</svg>
|
|
<span class="text-xl font-bold mr-4">한맥 가족사 S/W 업로드</span>
|
|
</div>
|
|
|
|
<div class="bg-white p-4 rounded shadow mb-6 flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<label for="product-select" class="font-bold">제품명(제품코드):</label>
|
|
<select id="product-select" class="p-2 border border-gray-300 rounded">
|
|
<option value="1">EG-BIM</option>
|
|
<option value="2">TOVA</option>
|
|
<option value="3">GAIA</option>
|
|
<option value="4">EG-BIM-MODELER</option>
|
|
<option value="999" selected>TEST</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<div>
|
|
<span class="text-sm">내부 배포 버전(최신):</span>
|
|
<span id="latest-family" class="text-blue-600 font-semibold">0.0.0</span>
|
|
</div>
|
|
<div class="flex items-center space-x-1">
|
|
<span class="text-sm">외부 배포 버전:</span>
|
|
<select id="external-version" class="p-1 border rounded">
|
|
<option value="">사용안함</option>
|
|
</select>
|
|
<button id="save-version" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">저장</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
<div class="flex items-center space-x-2">
|
|
<input type="checkbox" id="show-deleted" class="mr-1">
|
|
<label for="show-deleted" class="text-sm">삭제된 버전 표시</label>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<button id="btn-new" class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">+ 신규 버전</button>
|
|
<button class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600" id="btn-delete">파일삭제</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow rounded-lg overflow-x-auto max-h-[70vh] overflow-y-auto">
|
|
<table class="min-w-full text-sm text-left">
|
|
<thead class="bg-gray-200 sticky top-0 z-10">
|
|
<tr>
|
|
<th class="px-2 py-2 text-center w-[4%]"></th>
|
|
<th class="px-2 py-2 text-center w-[7%] whitespace-nowrap">버전</th>
|
|
<th class="px-2 py-2 text-center w-[20%] whitespace-nowrap">파일명</th>
|
|
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">파일타입</th>
|
|
<th class="px-2 py-2 text-center w-[8%] whitespace-nowrap">크기</th>
|
|
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">업로드일</th>
|
|
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">업로드수행자</th>
|
|
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">테스트완료</th>
|
|
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">테스트수행자</th>
|
|
<th class="px-2 py-2 text-center w-[12%] whitespace-nowrap">릴리즈일</th>
|
|
<th class="px-2 py-2 text-center w-[10%] whitespace-nowrap">릴리즈수행자</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="file-list-1" class="file-list hidden"></tbody>
|
|
<tbody id="file-list-2" class="file-list hidden"></tbody>
|
|
<tbody id="file-list-3" class="file-list hidden"></tbody>
|
|
<tbody id="file-list-4" class="file-list hidden"></tbody>
|
|
<tbody id="file-list-999" class="file-list"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50 hidden">
|
|
<div class="bg-white p-8 rounded shadow-lg w-full max-w-lg relative">
|
|
<h2 class="text-xl font-bold mb-6 text-center">신규 버전 업로드</h2>
|
|
|
|
<div class="space-y-3">
|
|
<div class="flex items-center">
|
|
<label class="w-28">S/W 버전 :</label>
|
|
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw1" min="0" max="99" required>
|
|
<span>.</span>
|
|
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw2" min="0" max="99" required>
|
|
<span>.</span>
|
|
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw3" min="0" max="999" required>
|
|
<span id="version-type-text" class="ml-3 font-bold text-gray-600">Major</span>
|
|
</div>
|
|
|
|
<div class="flex items-center mb-2">
|
|
<label class="w-36">설치파일(EXE):</label>
|
|
<input type="file" class="hidden" id="setup-file" accept=".exe">
|
|
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="setup-filename" readonly>
|
|
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="setup-add">추가</button>
|
|
</div>
|
|
<div class="flex items-center mb-2">
|
|
<label class="w-36">패치파일(EXE):</label>
|
|
<input type="file" class="hidden" id="patch-file" accept=".exe">
|
|
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="patch-filename" readonly>
|
|
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="patch-add">추가</button>
|
|
</div>
|
|
<div class="flex items-center mb-2">
|
|
<label class="w-36">릴리즈노트(MD):</label>
|
|
<input type="file" class="hidden" id="release-file" accept=".md">
|
|
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="release-filename" readonly>
|
|
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="release-add">추가</button>
|
|
</div>
|
|
<div class="flex items-center mb-2">
|
|
<label class="w-36">정보파일(ZIP):</label>
|
|
<input type="file" class="hidden" id="info-file">
|
|
<input type="text" class="border flex-1 p-1 bg-gray-100 ml-2" id="info-filename" readonly>
|
|
<button type="button" class="ml-2 px-3 py-1 bg-blue-500 text-white rounded" id="info-add">추가</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-2 mt-6">
|
|
<button id="upload-submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">업로드</button>
|
|
<button id="upload-cancel" class="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">취소</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 팝업 열기/닫기
|
|
$('#btn-new').on('click', ()=>$('#upload-modal').removeClass('hidden'));
|
|
$('#upload-cancel').on('click', ()=>$('#upload-modal').addClass('hidden'));
|
|
$('#upload-modal').on('click', function(e){ if(e.target===this) $(this).addClass('hidden'); });
|
|
|
|
// 파일 버튼
|
|
$('#setup-add').on('click', ()=>$('#setup-file').click());
|
|
$('#patch-add').on('click', ()=>$('#patch-file').click());
|
|
$('#info-add').on('click', ()=>$('#info-file').click());
|
|
$('#release-add').on('click', ()=>$('#release-file').click());
|
|
|
|
// 파일명 표시
|
|
$('#setup-file').on('change', function(){ $('#setup-filename').val(this.files[0]?.name || ''); });
|
|
$('#patch-file').on('change', function(){ $('#patch-filename').val(this.files[0]?.name || ''); });
|
|
$('#info-file').on('change', function(){ $('#info-filename').val(this.files[0]?.name || ''); });
|
|
$('#release-file').on('change', function(){ $('#release-filename').val(this.files[0]?.name || ''); });
|
|
|
|
function getCurrentUserName() {
|
|
return window.currentUserName || "알 수 없음";
|
|
}
|
|
|
|
// 업로드 실행
|
|
$('#upload-submit').on('click', async function() {
|
|
const productCode = $('#product-select').val();
|
|
const swVersion = [$('#sw1').val(), $('#sw2').val(), $('#sw3').val()].join('.');
|
|
const versionType = $('#version-type-text').text().toLowerCase();
|
|
const sw1 = $('#sw1').val();
|
|
const sw2 = $('#sw2').val();
|
|
const sw3 = $('#sw3').val();
|
|
|
|
if (!sw1 || !sw2 || !sw3) {
|
|
alert("S/W 버전을 모두 입력하세요.");
|
|
return;
|
|
}
|
|
|
|
const setupFile = $('#setup-file')[0].files[0];
|
|
const patchFile = $('#patch-file')[0].files[0];
|
|
const infoFile = $('#info-file')[0].files[0];
|
|
const releaseFile = $('#release-file')[0].files[0];
|
|
|
|
if (!setupFile) {
|
|
alert("설치파일(EXE)은 반드시 업로드해야 합니다.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let checkRes = await $.post("/egbim/sw_upload/upload_check_version.php", {
|
|
product_code: productCode,
|
|
sw_version: swVersion
|
|
});
|
|
|
|
if (checkRes.exists) {
|
|
const proceed = confirm(`기존에 업로드된 [${swVersion}] 버전이 존재합니다.\n파일을 덮어쓰겠습니까?`);
|
|
if (!proceed) return;
|
|
}
|
|
} catch (err) {
|
|
console.error("버전 확인 오류:", err);
|
|
alert("버전 확인 중 오류가 발생했습니다.");
|
|
return;
|
|
}
|
|
|
|
// ✅ 파일 규칙 이름 롤백 (텍스트 변환 제거, 원래대로 숫자 코드 사용)
|
|
const rename = (type, ext) => {
|
|
// 제품 코드와 제품명 매핑 (DB/서버 폴더 구조와 일치해야 함)
|
|
const productMap = {
|
|
"1": "eg-bim",
|
|
"2": "tova",
|
|
"3": "gaia",
|
|
"4": "eg-bim-modeler", // 수정된 부분: EG-BIM-MODELER 파일명 규칙 매핑 추가
|
|
"999": "test"
|
|
};
|
|
|
|
const prodName = productMap[productCode] || productCode; // 매핑 없으면 코드 그대로 사용
|
|
const ver = swVersion.toLowerCase();
|
|
|
|
switch (type) {
|
|
case "setup": return `setup_${prodName}_${ver}${ext}`;
|
|
case "patch": return `patch_${prodName}_${ver}${ext}`;
|
|
case "info": return `updateinfo_${prodName}_${ver}${ext}`;
|
|
case "md": return `releasenote_${prodName}_${ver}${ext}`;
|
|
}
|
|
};
|
|
|
|
const files = [];
|
|
if (setupFile) files.push({ orig: setupFile, type:"setup", ext:".exe" });
|
|
if (patchFile) files.push({ orig: patchFile, type:"patch", ext:".exe" });
|
|
if (infoFile) files.push({ orig: infoFile, type:"info", ext:"." + infoFile.name.split(".").pop() });
|
|
if (releaseFile) files.push({ orig: releaseFile, type:"md", ext:".md" });
|
|
|
|
try {
|
|
for (let f of files) {
|
|
const renamed = rename(f.type, f.ext);
|
|
|
|
let formData = new FormData();
|
|
formData.append("product_code", productCode);
|
|
formData.append("sw_version", swVersion);
|
|
formData.append("filename", renamed);
|
|
formData.append("content_type", f.orig.type || "application/octet-stream");
|
|
|
|
let presignRes = await fetch("/egbim/sw_upload/upload_presign.php", { method:"POST", body:formData });
|
|
let presignData = await presignRes.json();
|
|
|
|
let putRes = await fetch(presignData.url, {
|
|
method:"PUT",
|
|
headers:{ "Content-Type": f.orig.type || "application/octet-stream" },
|
|
body:f.orig
|
|
});
|
|
if (!putRes.ok) throw new Error(renamed+" 업로드 실패");
|
|
|
|
f.path = presignData.key;
|
|
f.name = renamed;
|
|
f.size = f.orig.size;
|
|
f.filetype = f.type;
|
|
}
|
|
|
|
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
|
|
|
$.post("/egbim/sw_upload/upload_popup.php", {
|
|
product_code: productCode,
|
|
sw_version: swVersion,
|
|
version_type: versionType,
|
|
uploader: userName,
|
|
files: JSON.stringify(files.map(f=>({
|
|
name: f.name,
|
|
original: f.orig.name,
|
|
path: f.path,
|
|
type: f.filetype,
|
|
size: f.size
|
|
})))
|
|
}, function(res){
|
|
if(res.group){
|
|
loadFileList(productCode);
|
|
alert("업로드 및 DB 저장 성공!");
|
|
}
|
|
}, "json");
|
|
|
|
$('#upload-modal').addClass('hidden');
|
|
|
|
} catch(err){
|
|
alert("업로드 중 오류: "+err.message);
|
|
}
|
|
});
|
|
|
|
$(document).ready(function(){
|
|
const productCode = $('#product-select').val();
|
|
loadFileList(productCode);
|
|
loadVersion(productCode);
|
|
|
|
$('#product-select').on('change', function(){
|
|
const newCode = $(this).val();
|
|
loadFileList(newCode);
|
|
loadVersion(newCode);
|
|
});
|
|
});
|
|
|
|
function loadFileList(productCode){
|
|
const showDeleted = $("#show-deleted").is(":checked") ? 1 : 0;
|
|
|
|
$.ajax({
|
|
url: "/egbim/sw_upload/upload_list.php",
|
|
type: "GET",
|
|
data: { product_code: productCode, show_deleted: showDeleted },
|
|
dataType: "json",
|
|
success: function(res){
|
|
const $tbody = $("#file-list-" + productCode);
|
|
$tbody.empty();
|
|
|
|
if(res.status !== "ok"){
|
|
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-red-500">조회 실패: ${res.message}</td></tr>`);
|
|
return;
|
|
}
|
|
|
|
if(res.files && res.files.length > 0){
|
|
renderFilesFromDB(res.files, $tbody);
|
|
} else {
|
|
$tbody.append(`<tr><td colspan="11" class="text-center py-4 text-gray-500">데이터가 없습니다.</td></tr>`);
|
|
}
|
|
},
|
|
error: function(xhr){
|
|
alert("리스트 조회 중 오류가 발생했습니다. (code: "+xhr.status+")");
|
|
}
|
|
});
|
|
}
|
|
|
|
$(document).on("change", "#show-deleted", function(){
|
|
const productCode = $("#product-select").val();
|
|
loadFileList(productCode);
|
|
});
|
|
|
|
function renderFilesFromDB(files, $tbody){
|
|
let grouped = {};
|
|
files.forEach(f=>{
|
|
let key = f.sw_version+"_"+f.upload_date;
|
|
if(!grouped[key]) grouped[key] = {meta:f, files:[]};
|
|
grouped[key].files.push(f);
|
|
});
|
|
|
|
Object.values(grouped).forEach(g=>{
|
|
let rowspan = g.files.length;
|
|
g.files.forEach((f, idx)=>{
|
|
// ✅ 직접 다운로드 API 주소 생성 (filepath와 filename을 인자로 전달)
|
|
const downloadUrl = `/egbim/sw_upload/upload_download.php?path=${encodeURIComponent(f.filepath)}&name=${encodeURIComponent(f.filename)}`;
|
|
|
|
$tbody.append(`
|
|
<tr class="border-b">
|
|
${idx===0 ? `
|
|
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}" rowspan="${rowspan}">
|
|
${f.deleted==1 ? '' : `<input type="checkbox" value="${f.product_code}|${f.sw_version}"/>`}
|
|
</td>
|
|
<td class="px-4 py-2 font-bold text-blue-600 text-center" rowspan="${rowspan}">
|
|
${f.sw_version}
|
|
</td>
|
|
` : ''}
|
|
|
|
<td class="px-4 py-2 ${f.deleted==1?'bg-red-100':''}">
|
|
<a href="${downloadUrl}" class="text-blue-600 hover:text-blue-800 hover:underline flex items-center" target="_blank">
|
|
<i class="fa-solid fa-file-arrow-down mr-2 opacity-70"></i>
|
|
${f.filename}
|
|
</a>
|
|
</td>
|
|
|
|
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">${f.filetype}</td>
|
|
<td class="px-4 py-2 text-center ${f.deleted==1?'bg-red-100':''}">
|
|
${f.filetype==="md"
|
|
? (Math.round(f.filesize/1024*100)/100)+"KB"
|
|
: (Math.round(f.filesize/1024/1024*100)/100)+"MB"}
|
|
</td>
|
|
|
|
${idx===0?`
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.upload_date}</td>
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.uploader}</td>
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
|
${f.deleted==1
|
|
? '<span class="text-red-500">삭제됨</span>'
|
|
: (f.test_done_date
|
|
? f.test_done_date
|
|
: `<button class="bg-green-500 text-white px-2 py-1 rounded btn-testdone"
|
|
data-product="${f.product_code}"
|
|
data-version="${f.sw_version}">완료확정</button>`)}
|
|
</td>
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.tester || ''}</td>
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">
|
|
${f.deleted==1
|
|
? '<span class="text-red-500">삭제됨</span>'
|
|
: (f.release_date
|
|
? f.release_date
|
|
: `<button class="bg-blue-500 text-white px-2 py-1 rounded btn-release"
|
|
data-product="${f.product_code}"
|
|
data-version="${f.sw_version}">배포확정</button>`)}
|
|
</td>
|
|
<td class="px-4 py-2 text-center" rowspan="${rowspan}">${f.releaser || ''}</td>
|
|
`:''}
|
|
</tr>
|
|
`);
|
|
});
|
|
});
|
|
}
|
|
|
|
$('#product-select').on('change', function(){
|
|
const newCode = $(this).val();
|
|
$(".file-list").addClass("hidden");
|
|
$("#file-list-" + newCode).removeClass("hidden");
|
|
loadFileList(newCode);
|
|
loadVersion(newCode);
|
|
});
|
|
|
|
function updateVersionType() {
|
|
const major = parseInt($('#sw1').val() || 0, 10);
|
|
const minor = parseInt($('#sw2').val() || 0, 10);
|
|
const patch = parseInt($('#sw3').val() || 0, 10);
|
|
|
|
let typeText = 'MAJOR';
|
|
if (patch > 0) {
|
|
typeText = 'PATCH';
|
|
} else if (minor > 0) {
|
|
typeText = 'MINOR';
|
|
} else if (major > 0) {
|
|
typeText = 'MAJOR';
|
|
}
|
|
|
|
$('#version-type-text').text(typeText);
|
|
|
|
if (patch > 0) {
|
|
$('#patch-file').prop('disabled', false);
|
|
$('#patch-filename').prop('disabled', false);
|
|
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
|
} else {
|
|
$('#patch-file').prop('disabled', true).val('');
|
|
$('#patch-filename').prop('disabled', true).val('');
|
|
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
|
}
|
|
}
|
|
|
|
$('#sw1, #sw2, #sw3').on('input', updateVersionType);
|
|
updateVersionType();
|
|
|
|
$(document).on('click', '.btn-testdone', function(){
|
|
const productCode = $(this).data('product');
|
|
const swVersion = $(this).data('version');
|
|
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
|
|
|
$.post("/egbim/sw_upload/upload_testdone.php", {
|
|
product_code: productCode,
|
|
sw_version: swVersion,
|
|
tester: userName
|
|
}, function(res){
|
|
if(res.test_done_date){
|
|
$(`#file-list tr`).each(function(){
|
|
const version = $(this).find("td:nth-child(2)").text().trim();
|
|
if(version === swVersion){
|
|
$(this).find("td").eq(7).text(res.test_done_date);
|
|
$(this).find("td").eq(8).text(res.tester);
|
|
}
|
|
});
|
|
alert("테스트 완료되었습니다.");
|
|
loadFileList(productCode);
|
|
}
|
|
}, "json");
|
|
});
|
|
|
|
$(document).on("click", ".btn-release", function(){
|
|
const productCode = $(this).data("product");
|
|
const swVersion = $(this).data("version");
|
|
const userName = sessionStorage.getItem("userName") || "알 수 없음";
|
|
|
|
// ✅ 1. 사용자에게 제시할 확인 문구 설정
|
|
const confirmText = "배포 완료 요청";
|
|
|
|
// ✅ 2. 입력창(prompt) 띄우기
|
|
const userInput = prompt(`[버전 ${swVersion}] 릴리즈 서버로 이동하시겠습니까?\n\n진행하시려면 하단에 "${confirmText}" 문구를 정확히 입력해주세요.`);
|
|
|
|
// ✅ 3. 검증 로직
|
|
if (userInput === null) return; // '취소'를 누른 경우
|
|
|
|
if (userInput.trim() !== confirmText) {
|
|
alert(`입력한 문구가 일치하지 않습니다.\n(입력: ${userInput})`);
|
|
return;
|
|
}
|
|
|
|
// ✅ 4. 문구가 일치할 경우에만 기존 서버 통신(AJAX) 실행
|
|
$.post("/egbim/sw_upload/upload_release.php", {
|
|
product_code: productCode,
|
|
sw_version: swVersion,
|
|
releaser: userName
|
|
}, function(res){
|
|
if(res.release_date){
|
|
$(`#file-list tr`).each(function(){
|
|
const version = $(this).find("td:nth-child(2)").text().trim();
|
|
if(version === swVersion){
|
|
$(this).find("td").eq(9).text(res.release_date);
|
|
$(this).find("td").eq(10).text(res.releaser);
|
|
}
|
|
});
|
|
alert(`[버전 ${swVersion}] 배포가 성공적으로 확정되었습니다.`);
|
|
loadFileList(productCode);
|
|
} else {
|
|
alert("실패: " + (res.message || "배포 확정 처리 중 오류 발생"));
|
|
}
|
|
}, "json");
|
|
});
|
|
|
|
$(document).on("click", "#btn-delete", function () {
|
|
const checked = $("input[type=checkbox]:checked").map(function () { return $(this).val(); }).get();
|
|
if (checked.length === 0) {
|
|
alert("삭제할 버전을 선택하세요.");
|
|
return;
|
|
}
|
|
if (!confirm("선택한 버전 전체 파일을 삭제하시겠습니까?")) return;
|
|
|
|
checked.forEach(item => {
|
|
const [product, version] = item.split("|");
|
|
$.post("/egbim/sw_upload/upload_delete.php", {
|
|
product_code: product,
|
|
sw_version: version
|
|
}, function(res) {
|
|
if (res.status === "ok") {
|
|
alert(`[${version}] 삭제 완료!`);
|
|
loadFileList(product);
|
|
} else {
|
|
if (res.failed && res.failed.length > 0) {
|
|
let errors = res.failed.map(f => `${f.file}: ${f.error}`).join("\n");
|
|
alert("삭제 실패:\n" + errors);
|
|
} else {
|
|
alert("삭제 실패: " + (res.message || "알 수 없는 오류"));
|
|
}
|
|
}
|
|
}, "json");
|
|
});
|
|
});
|
|
|
|
// ✅ 저장/변경 버튼 클릭 이벤트 수정
|
|
$(document).on("click", "#save-version", function() {
|
|
const productCode = $("#product-select").val();
|
|
const version = $("#external-version").val(); // "사용안함" 선택 시 "" 값이 전달됨
|
|
|
|
const msg = (version && version !== "")
|
|
? `외부 배포를 [ ${version} ] 버전으로 변경하시겠습니까?`
|
|
: `외부 배포를 '사용안함'으로 설정하시겠습니까?`;
|
|
|
|
if (!confirm(msg)) return;
|
|
|
|
$.post("/egbim/sw_upload/upload_version_update.php", {
|
|
product_code: productCode,
|
|
version: version // 백엔드에서 ""를 받으면 내부 버전과 동일하게 맞춰 "사용안함" 처리함
|
|
}, function(res) {
|
|
if (res.status === "ok") {
|
|
alert("배포 버전 설정이 완료되었습니다.");
|
|
loadVersion(productCode); // UI 새로고침
|
|
} else {
|
|
alert("설정 실패: " + res.message);
|
|
}
|
|
}, "json");
|
|
});
|
|
|
|
// ✅ 외부 배포 버전 정보 및 선택 목록 로드 함수 수정
|
|
function loadVersion(productCode) {
|
|
$.getJSON("/egbim/sw_upload/upload_version_get.php", { product_code: productCode }, function(res) {
|
|
if (res.status === "ok") {
|
|
// ✅ 이 코드가 있어야 "0.0.0" 대신 "1.2.0" 등의 실제 버전이 화면에 보입니다.
|
|
$("#latest-family").text(res.family_version);
|
|
|
|
const $sel = $("#external-version");
|
|
$sel.empty();
|
|
|
|
// 목록 그리기
|
|
if (res.confirmed_versions) {
|
|
res.confirmed_versions.forEach(v => {
|
|
const label = (v === "") ? "사용안함" : v;
|
|
$sel.append(`<option value="${v}">${label}</option>`);
|
|
});
|
|
}
|
|
|
|
$sel.val(res.external_version);
|
|
|
|
if (res.external_version !== "") {
|
|
$("#save-version").text("변경").removeClass("bg-blue-500").addClass("bg-yellow-500");
|
|
} else {
|
|
$("#save-version").text("저장").removeClass("bg-yellow-500").addClass("bg-blue-500");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function bindRangeCorrection(selector) {
|
|
$(selector).on("input", function() {
|
|
let val = parseInt($(this).val(), 10);
|
|
let min = parseInt($(this).attr("min"), 10) || 0;
|
|
let max = parseInt($(this).attr("max"), 10);
|
|
|
|
if (isNaN(val)) {
|
|
$(this).val("");
|
|
} else if (val < min) {
|
|
$(this).val(min);
|
|
} else if (val > max) {
|
|
$(this).val(max);
|
|
}
|
|
});
|
|
}
|
|
|
|
bindRangeCorrection("#sw1, #sw2, #sw3");
|
|
|
|
$('#btn-new').on('click', ()=>{
|
|
$('#setup-file, #patch-file, #release-file, #info-file').val('');
|
|
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
|
|
|
|
const latestVer = $('#latest-family').text().trim();
|
|
if (latestVer) {
|
|
const parts = latestVer.split('.');
|
|
$('#sw1').val(parts[0] || 0);
|
|
$('#sw2').val(parts[1] || 0);
|
|
$('#sw3').val(parts[2] || 0);
|
|
}
|
|
|
|
const patch = parseInt($('#sw3').val() || 0, 10);
|
|
if (patch > 0) {
|
|
$('#patch-file').prop('disabled', false);
|
|
$('#patch-filename').prop('disabled', false);
|
|
$('#patch-add').prop('disabled', false).removeClass('bg-gray-400').addClass('bg-blue-500');
|
|
} else {
|
|
$('#patch-file').prop('disabled', true).val('');
|
|
$('#patch-filename').prop('disabled', true).val('');
|
|
$('#patch-add').prop('disabled', true).removeClass('bg-blue-500').addClass('bg-gray-400');
|
|
}
|
|
|
|
updateVersionType();
|
|
$('#upload-modal').removeClass('hidden');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|