Files
2026-07-23 16:15:57 +09:00

794 lines
32 KiB
PHP

<?php
require_once $_SERVER['DOCUMENT_ROOT'].'/eng/skin/member/basic/descope_session.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/eng/bbs/upload_admin_guard.php';
// 로그인 안 되었거나 관리자 아니면 바로 로그인 화면으로
if (empty($_SESSION['user']['userId']) || !is_upload_admin()) {
header("Location: /eng/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="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>
<input type="number" id="ver1" class="w-14 p-1 border rounded" min="0" max="99">
<span class="text-sm">.</span>
<input type="number" id="ver2" class="w-14 p-1 border rounded" min="0" max="99">
<span class="text-sm">.</span>
<input type="number" id="ver3" class="w-14 p-1 border rounded" min="0" max="99">
</div>
<button id="save-version" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">저장</button> -->
<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>
<!-- ✅ 서버에서 confirmed 버전 목록을 가져와 채움 -->
</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-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600" id="btn-download">다운로드</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 -->
<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-999" class="file-list"></tbody> <!-- TEST 기본 표시 -->
</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(); // eg-bim
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();
// ✅ S/W 버전 필수 체크
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("/eng/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) => {
const prod = productCode; // 이미 소문자 값 들어옴
const ver = swVersion.toLowerCase();
switch (type) {
case "setup": return `setup_${prod}_${ver}${ext}`;
case "patch": return `patch_${prod}_${ver}${ext}`;
case "info": return `updateinfo_${prod}_${ver}${ext}`;
case "md": return `releasenote_${prod}_${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 {
// 1) Presigned 업로드
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");
// presign 요청
let presignRes = await fetch("/eng/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+" 업로드 실패");
// DB용 정보 세팅
f.path = presignData.key;
f.name = renamed;
f.size = f.orig.size;
f.filetype = f.type;
}
const userName = sessionStorage.getItem("userName") || "알 수 없음";
const loginId = sessionStorage.getItem("loginId") || "";
// 2) DB Insert
$.post("/eng/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');
// $('.text-blue-600').text(swVersion);
} 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: "/eng/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)=>{
$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':''}">${f.filename}</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);
});
// ✅ 초기 로드 시 TEST 불러오기
$(document).ready(function(){
const productCode = $('#product-select').val();
loadFileList(productCode);
loadVersion(productCode);
});
// 버전 입력 시 타입 자동 결정
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);
// ✅ PATCH 영역 입력값에 따라 패치파일 업로드창 활성화/비활성화
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") || "알 수 없음";
const loginId = sessionStorage.getItem("loginId") || "";
$.post("/eng/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(); // 5번째 열 = 버전
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") || "알 수 없음";
const loginId = sessionStorage.getItem("loginId") || "";
if(!confirm(`[버전 ${swVersion}] 릴리즈 서버로 이동하시겠습니까?`)) return;
$.post("/eng/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(); // ✅ 2번째 열 = 버전
if(version === swVersion){
$(this).find("td").eq(9).text(res.release_date); // ✅ 10번째 열 = 릴리즈일
$(this).find("td").eq(10).text(res.releaser); // ✅ 11번째 열 = 릴리즈자
}
});
alert("배포가 확정되었습니다."); // ✅ 알림
loadFileList(productCode); // ✅ 리스트 전체 새로고침
} else {
alert("실패: " + (res.message || "배포 확정 처리 중 오류 발생"));
}
}, "json");
});
// ✅ 다운로드 버튼 클릭
$(document).on("click", "#btn-download", function () {
const checked = $("input[type=checkbox]:checked")
.map(function () {
return $(this).val();
})
.get();
if (checked.length === 0) {
alert("다운로드할 버전을 선택하세요.");
return;
}
checked.forEach(val => {
const [product, version] = val.split("|");
// ✅ 요청 URL 생성
const url = "/eng/sw_upload/upload_download.php?product_code=" + encodeURIComponent(product) +
"&sw_version=" + encodeURIComponent(version);
console.log("다운로드 요청:", url); // 디버깅 로그
// ✅ a 태그를 만들어 강제 클릭 (download 속성 제거)
const link = document.createElement("a");
link.href = url;
link.target = "_blank"; // 새 탭 열기 → 다운로드 트리거 보장
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
});
// ✅ 삭제 버튼 클릭
$(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("/eng/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 = [
// $("#ver1").val() || 0,
// $("#ver2").val() || 0,
// $("#ver3").val() || 0
// ].join(".");
// $.post("/eng/sw_upload/upload_version_update.php", {
// product_code: productCode,
// version: version
// }, function(res){
// if(res.status === "ok"){
// alert("외부 배포 버전 저장/변경 완료!");
// loadVersion(productCode); // 저장 후 새 값 반영
// } else {
// alert("실패: " + res.message);
// }
// }, "json");
// });
$(document).on("click", "#save-version", function(){
const productCode = $("#product-select").val();
const version = $("#external-version").val(); // ✅ 콤보박스 값
// ✅ confirm 추가
const msg = version
? `외부 배포를 ${version} 버전으로 변경하시겠습니까?`
: `외부 배포를 '사용안함'으로 변경하시겠습니까?`;
if(!confirm(msg)) return; // ❌ 취소 시 중단
$.post("/eng/sw_upload/upload_version_update.php", {
product_code: productCode,
version: version
}, function(res){
if(res.status === "ok"){
// alert("외부 배포 버전 저장/변경 완료!");
loadVersion(productCode); // 저장 후 새 값 반영
} else {
alert("실패: " + res.message);
}
}, "json");
});
// 페이지 로드 시 현재 버전 불러오기
// function loadVersion(productCode){
// $.getJSON("/eng/sw_upload/upload_version_get.php", { product_code: productCode }, function(res){
// if(res.status === "ok"){
// // 내부 최신 버전 표시
// $("#latest-family").text(res.family_version);
// // 외부 버전 입력칸 채우기
// const parts = res.external_version.split(".");
// $("#ver1").val(parts[0] || 0);
// $("#ver2").val(parts[1] || 0);
// $("#ver3").val(parts[2] || 0);
// // 버튼 상태 (저장 vs 변경)
// if(res.external_exists){
// $("#save-version")
// .text("변경")
// .data("mode","update")
// .removeClass("bg-blue-500 hover:bg-blue-600")
// .addClass("bg-yellow-500 hover:bg-yellow-600");
// } else {
// $("#save-version")
// .text("저장")
// .data("mode","save")
// .removeClass("bg-yellow-500 hover:bg-yellow-600")
// .addClass("bg-blue-500 hover:bg-blue-600");
// }
// }
// });
// }
function loadVersion(productCode){
$.getJSON("/eng/sw_upload/upload_version_get.php", { product_code: productCode }, function(res){
if(res.status === "ok"){
// 내부 최신 버전 표시
$("#latest-family").text(res.family_version);
// 외부 버전 콤보박스 채우기
const $sel = $("#external-version");
$sel.empty();
$sel.append(`<option value="">사용안함</option>`);
if(res.confirmed_versions && res.confirmed_versions.length > 0){
res.confirmed_versions.forEach(v=>{
$sel.append(`<option value="${v}">${v}</option>`);
});
}
// 현재 저장된 외부 버전 선택
if(res.external_version){
$sel.val(res.external_version);
} else {
$sel.val("");
}
// 버튼 상태 (저장 vs 변경)
if(res.external_exists){
$("#save-version")
.text("변경")
.data("mode","update")
.removeClass("bg-blue-500 hover:bg-blue-600")
.addClass("bg-yellow-500 hover:bg-yellow-600");
} else {
$("#save-version")
.text("저장")
.data("mode","save")
.removeClass("bg-yellow-500 hover:bg-yellow-600")
.addClass("bg-blue-500 hover:bg-blue-600");
}
}
});
}
// 숫자 입력 자동 보정 (0~max 범위)
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', ()=>{
// 🔄 파일 input 초기화
$('#setup-file, #patch-file, #release-file, #info-file').val('');
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
// 내부 최신 버전 가져오기
const latestVer = $('#latest-family').text().trim(); // "1.2.3"
if (latestVer) {
const parts = latestVer.split('.');
$('#sw1').val(parts[0] || 0);
$('#sw2').val(parts[1] || 0);
$('#sw3').val(parts[2] || 0);
}
// patch 여부 확인 (최신 버전의 세 번째 자리)
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(); // 버전 타입 갱신 (Major/Minor/Patch)
$('#upload-modal').removeClass('hidden');
});
// 팝업 열기
$('#btn-new').on('click', ()=>{
// 🔄 파일 input만 초기화
$('#setup-file, #patch-file, #release-file, #info-file').val('');
// 파일명 표시란 초기화
$('#setup-filename, #patch-filename, #release-filename, #info-filename').val('');
// 패치파일 버튼 상태 리셋 (단, PATCH 값이 있으면 활성화 유지)
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');
}
$('#upload-modal').removeClass('hidden');
});
</script>
</body>
</html>