794 lines
32 KiB
PHP
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>
|