최초 커밋
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ⭐️ 여기에 추가!
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$family_version = $_POST['family_version'] ?? '';
|
||||
$external_version = $_POST['external_version'] ?? '';
|
||||
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패: ' . $conn->connect_error]);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// FK 에러 방지: products 테이블에 존재하는지 확인
|
||||
$chk = $conn->prepare("SELECT COUNT(*) FROM products WHERE code=?");
|
||||
$chk->bind_param("s", $product_code);
|
||||
$chk->execute();
|
||||
$chk->bind_result($cnt);
|
||||
$chk->fetch();
|
||||
$chk->close();
|
||||
if ($cnt < 1) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '존재하지 않는 제품코드입니다']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 실제 insert/update 쿼리 실행
|
||||
$sql = "INSERT INTO deploy_versions (product_code, family_version, external_version)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version=VALUES(family_version),
|
||||
external_version=VALUES(external_version)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => '쿼리 준비 오류: ' . $conn->error]);
|
||||
exit;
|
||||
}
|
||||
$stmt->bind_param("sss", $product_code, $family_version, $external_version);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['message' => '저장 완료']);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 오류: ' . $stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// upload_common.php
|
||||
// 파일 업로드 전용 공통 초기화
|
||||
|
||||
// (1) Descope 세션 불러오기
|
||||
require_once __DIR__ . '/../skin/member/basic/descope_session.php';
|
||||
|
||||
// (2) 현재 로그인 사용자 정보 안전하게 꺼내기
|
||||
$currentUserId = $_SESSION['user']['loginIds'][0] ?? '';
|
||||
$currentUserName = $_SESSION['user']['name'] ?? '알 수 없음';
|
||||
$currentUserEmail= $_SESSION['user']['email'] ?? '';
|
||||
|
||||
// (3) 관리자 여부 플래그
|
||||
$ADMIN_EMAILS = [
|
||||
'kjy0426@hanmaceng.co.kr',
|
||||
'b24014@hanmaceng.co.kr',
|
||||
'b23065@hanmaceng.co.kr',
|
||||
'b23008@baroncs.co.kr',
|
||||
'cjy627@hanmaceng.co.kr',
|
||||
'b23072@hanmaceng.co.kr',
|
||||
];
|
||||
$isAdmin = in_array($currentUserId, $ADMIN_EMAILS, true);
|
||||
|
||||
// (4) JSON 응답 시 헤더 자동 세팅 (필요 시)
|
||||
if (!headers_sent()) {
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$pdo = new PDO("mysql:host=localhost;dbname=egbim;charset=utf8mb4","egbim","baron3840!!",[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||
]);
|
||||
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
$swVersion = $_POST['sw_version'] ?? '';
|
||||
|
||||
$st = $pdo->prepare("
|
||||
SELECT COUNT(*) FROM deploy_files
|
||||
WHERE product_code=? AND sw_version=? AND deleted=0
|
||||
");
|
||||
$st->execute([$productCode, $swVersion]);
|
||||
|
||||
echo json_encode(['exists' => $st->fetchColumn() > 0]);
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
// // /egbim/sw_upload/upload_delete.php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
// ini_set('display_errors', 1);
|
||||
// error_reporting(E_ALL);
|
||||
|
||||
// require __DIR__ . '/../vendor/autoload.php'; // AWS SDK
|
||||
|
||||
// use Aws\S3\S3Client;
|
||||
// use Aws\Exception\AwsException;
|
||||
|
||||
// // === DB 연결 ===
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['status' => 'fail', 'message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 파라미터 ===
|
||||
// $ids = $_POST['ids'] ?? [];
|
||||
// if (!is_array($ids) || empty($ids)) {
|
||||
// http_response_code(400);
|
||||
// echo json_encode(['status' => 'fail', 'message' => '삭제할 ID가 없습니다.']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === S3 클라이언트 ===
|
||||
// $bucket = 'baron-software-test'; // 삭제는 test 버킷에서만
|
||||
// $s3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
// 'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// $success = [];
|
||||
// $failed = [];
|
||||
|
||||
// foreach ($ids as $id) {
|
||||
// $id = (int)$id;
|
||||
// $res = $conn->query("SELECT * FROM deploy_files WHERE id={$id} AND deleted=0");
|
||||
// $file = $res->fetch_assoc();
|
||||
// if (!$file) {
|
||||
// $failed[] = ['id' => $id, 'error' => '파일 없음'];
|
||||
// continue;
|
||||
// }
|
||||
// // ❌ 릴리즈된 경우 삭제 금지
|
||||
// if (!empty($file['release_date'])) {
|
||||
// $failed[] = ['error' => '이미 릴리즈된 파일은 삭제할 수 없습니다.'];
|
||||
// continue;
|
||||
// }
|
||||
// $objectKey = $file['filepath'];
|
||||
|
||||
// try {
|
||||
// // 1) S3 객체 삭제
|
||||
// $s3->deleteObject([
|
||||
// 'Bucket' => $bucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 2) DB 플래그 업데이트
|
||||
// $conn->query("UPDATE deploy_files SET deleted=1 WHERE id={$id}");
|
||||
|
||||
// // 3) ✅ 최신 내부 버전 갱신
|
||||
// $productCode = $conn->real_escape_string($file['product_code']);
|
||||
|
||||
// // 삭제 후 남아있는 최신 버전 조회
|
||||
// $verRes = $conn->prepare("
|
||||
// SELECT sw_version
|
||||
// FROM deploy_files
|
||||
// WHERE product_code=? AND deleted=0
|
||||
// ORDER BY
|
||||
// CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
// CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
// CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
// LIMIT 1
|
||||
// ");
|
||||
// $verRes->bind_param("s", $productCode);
|
||||
// $verRes->execute();
|
||||
// $newLatest = $verRes->get_result()->fetch_assoc()['sw_version'] ?? null;
|
||||
|
||||
// if ($newLatest) {
|
||||
// // 남아있는 최신 버전으로 업데이트
|
||||
// $conn->query("
|
||||
// UPDATE deploy_versions_test
|
||||
// SET family_version='{$newLatest}', updated_at=NOW()
|
||||
// WHERE product_code='{$productCode}'
|
||||
// ");
|
||||
// } else {
|
||||
// // 남은 버전이 없으면 초기화
|
||||
// $conn->query("
|
||||
// UPDATE deploy_versions_test
|
||||
// SET family_version='0.0.0', updated_at=NOW()
|
||||
// WHERE product_code='{$productCode}'
|
||||
// ");
|
||||
// }
|
||||
|
||||
// $success[] = $id; // ✅ id 로 반환
|
||||
// } catch (AwsException $e) {
|
||||
// $failed[] = ['id' => $id, 'error' => $e->getAwsErrorMessage()];
|
||||
// }
|
||||
// }
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => empty($failed) ? 'ok' : 'partial',
|
||||
// 'deleted' => $success,
|
||||
// 'failed' => $failed,
|
||||
// ]);
|
||||
?>
|
||||
<?php
|
||||
// /egbim/sw_upload/upload_delete.php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php'; // AWS SDK
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
// === DB 연결 ===
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'fail', 'message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
$swVersion = $_POST['sw_version'] ?? '';
|
||||
|
||||
if (!$productCode || !$swVersion) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'fail', 'message' => '삭제할 대상이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === S3 클라이언트 ===
|
||||
$bucket = 'baron-software-test'; // 삭제는 test 버킷에서만
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
]
|
||||
]);
|
||||
|
||||
$success = [];
|
||||
$failed = [];
|
||||
|
||||
// === 버전 단위 전체 파일 조회 ===
|
||||
$res = $conn->query("
|
||||
SELECT * FROM deploy_files
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
AND sw_version='{$conn->real_escape_string($swVersion)}'
|
||||
AND deleted=0
|
||||
");
|
||||
$files = $res->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
if (!$files) {
|
||||
echo json_encode(['status'=>'fail','message'=>'해당 버전의 파일이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 삭제 처리 루프 ===
|
||||
foreach ($files as $file) {
|
||||
// ❌ 릴리즈된 경우 → 전체 삭제 불가 처리
|
||||
if (!empty($file['release_date'])) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '이미 릴리즈된 파일은 삭제할 수 없습니다.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 릴리즈 안 된 경우만 여기 도달 → 전체 삭제 진행
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
// 1) S3 객체 삭제
|
||||
$s3->deleteObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $file['filepath'],
|
||||
]);
|
||||
|
||||
// 2) DB 플래그 업데이트
|
||||
$conn->query("UPDATE deploy_files SET deleted=1 WHERE id={$file['id']}");
|
||||
|
||||
$success[] = $file['filename'];
|
||||
} catch (AwsException $e) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '파일 삭제 중 오류: '.$e->getAwsErrorMessage()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// === 최신 내부 버전 갱신 ===
|
||||
$verRes = $conn->query("
|
||||
SELECT sw_version
|
||||
FROM deploy_files
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
AND deleted=0
|
||||
ORDER BY
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$newLatest = $verRes->fetch_assoc()['sw_version'] ?? '0.0.0';
|
||||
|
||||
$conn->query("
|
||||
UPDATE deploy_versions_test
|
||||
SET family_version='{$newLatest}', updated_at=NOW()
|
||||
WHERE product_code='{$conn->real_escape_string($productCode)}'
|
||||
");
|
||||
|
||||
echo json_encode([
|
||||
'status' => empty($failed) ? 'ok' : 'fail',
|
||||
'deleted' => $success,
|
||||
'failed' => $failed,
|
||||
'new_latest' => $newLatest ?? '0.0.0',
|
||||
'message' => empty($failed)
|
||||
? '삭제 완료'
|
||||
: '삭제 실패: 이미 릴리즈된 파일이 있습니다.'
|
||||
]);
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
// require __DIR__ . '/../vendor/autoload.php';
|
||||
// use Aws\S3\S3Client;
|
||||
// use Aws\Exception\AwsException;
|
||||
|
||||
// header("Content-Type: application/zip");
|
||||
// header("Content-Disposition: attachment; filename=download.zip");
|
||||
|
||||
// $product = $_GET['product_code'] ?? '';
|
||||
// $version = $_GET['sw_version'] ?? '';
|
||||
|
||||
// if (!$product || !$version) {
|
||||
// http_response_code(400);
|
||||
// echo "필수값 없음";
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === DB 연결 ===
|
||||
// $conn = new mysqli("localhost", "egbim", "baron3840!!", "egbim");
|
||||
// mysqli_set_charset($conn, "utf8mb4");
|
||||
|
||||
// $res = $conn->query("SELECT filename, original_name, filepath FROM deploy_files
|
||||
// WHERE product_code='".$conn->real_escape_string($product)."'
|
||||
// AND sw_version='".$conn->real_escape_string($version)."'
|
||||
// AND deleted=0");
|
||||
// $files = $res->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
// if (empty($files)) {
|
||||
// echo "파일 없음";
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === S3 Client ===
|
||||
// $s3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint'=> 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
// 'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// // === 임시 ZIP 파일 ===
|
||||
// $tmpZip = tempnam(sys_get_temp_dir(), "zip");
|
||||
// $zip = new ZipArchive();
|
||||
// $zip->open($tmpZip, ZipArchive::OVERWRITE);
|
||||
|
||||
// foreach ($files as $f) {
|
||||
// $key = $f['filepath']; // 예: 999/1.4.6/releasenote_999_1.4.6.md
|
||||
// $saveName = $f['original_name'] ?: basename($f['filename']);
|
||||
|
||||
// try {
|
||||
// $obj = $s3->getObject([
|
||||
// 'Bucket' => 'baron-software-release',
|
||||
// 'Key' => $key
|
||||
// ]);
|
||||
// $content = (string)$obj['Body'];
|
||||
|
||||
// if ($content !== '') {
|
||||
// $zip->addFromString($saveName, $content);
|
||||
// }
|
||||
// } catch (AwsException $e) {
|
||||
// error_log("다운로드 실패: $key - " . $e->getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
// $zip->close();
|
||||
|
||||
// // === ZIP 출력 ===
|
||||
// readfile($tmpZip);
|
||||
// unlink($tmpZip);
|
||||
// exit;
|
||||
?>
|
||||
|
||||
<?php
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
$product = $_GET['product_code'] ?? '';
|
||||
$version = $_GET['sw_version'] ?? '';
|
||||
|
||||
if (!$product || !$version) {
|
||||
http_response_code(400);
|
||||
exit("필수값 없음");
|
||||
}
|
||||
|
||||
// === DB 연결 ===
|
||||
$conn = new mysqli("localhost", "egbim", "baron3840!!", "egbim");
|
||||
mysqli_set_charset($conn, "utf8mb4");
|
||||
|
||||
$sql = "SELECT filename, original_name, filepath, release_date
|
||||
FROM deploy_files
|
||||
WHERE product_code=? AND sw_version=? AND deleted=0";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("ss", $product, $version);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$files = $res->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
|
||||
if (empty($files)) {
|
||||
http_response_code(404);
|
||||
exit("파일 없음");
|
||||
}
|
||||
|
||||
// === S3 Client ===
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint'=> 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
]
|
||||
]);
|
||||
|
||||
// === ZIP 생성 ===
|
||||
$tmpZip = tempnam(sys_get_temp_dir(), "zip");
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($tmpZip, ZipArchive::OVERWRITE);
|
||||
|
||||
foreach ($files as $f) {
|
||||
$key = $f['filepath'];
|
||||
$saveName = $f['original_name'] ?: basename($f['filename']);
|
||||
$bucket = $f['release_date'] ? 'baron-software-release' : 'baron-software-test';
|
||||
|
||||
try {
|
||||
$obj = $s3->getObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $key
|
||||
]);
|
||||
$content = (string)$obj['Body'];
|
||||
$zip->addFromString($saveName, $content);
|
||||
} catch (AwsException $e) {
|
||||
// 실패한 파일은 스킵 (ZIP은 정상 유지)
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
// === ZIP 전송 ===
|
||||
if (ob_get_level()) ob_end_clean();
|
||||
header("Content-Type: application/zip");
|
||||
header("Content-Disposition: attachment; filename=\"{$product}_{$version}.zip\"");
|
||||
header("Content-Length: " . filesize($tmpZip));
|
||||
|
||||
readfile($tmpZip);
|
||||
unlink($tmpZip);
|
||||
exit;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// // require_once __DIR__ . '/upload_common.php';
|
||||
// // require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/bbs/admin_guard.php';
|
||||
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 선택된 제품코드 파라미터 ===
|
||||
// $product_code = $_GET['product_code'] ?? '';
|
||||
|
||||
// // TODO: 권한 부여는 추후 적용
|
||||
// // $user_role = $_SESSION['user']['role'] ?? 'user'; // 예: admin, manager, user
|
||||
// // if ($user_role !== 'admin') {
|
||||
// // // 권한 체크 로직 들어갈 자리
|
||||
// // }
|
||||
|
||||
// $sql = "SELECT id, product_code, sw_version, version_type,
|
||||
// filename, filepath, filetype,
|
||||
// filesize, uploader,
|
||||
// DATE_FORMAT(upload_date, '%Y-%m-%d %H:%i:%s') as upload_date,
|
||||
// DATE_FORMAT(test_done_date, '%Y-%m-%d %H:%i:%s') as test_done_date,
|
||||
// tester,
|
||||
// DATE_FORMAT(release_date, '%Y-%m-%d %H:%i:%s') as release_date,
|
||||
// releaser,
|
||||
// deleted
|
||||
// FROM deploy_files
|
||||
// WHERE 1=1";
|
||||
|
||||
// // 제품코드가 지정된 경우만 필터
|
||||
// if ($product_code !== '') {
|
||||
// $sql .= " AND product_code = '". $conn->real_escape_string($product_code) ."'";
|
||||
// }
|
||||
|
||||
// $sql .= " ORDER BY id DESC";
|
||||
|
||||
// $res = $conn->query($sql);
|
||||
// $files = [];
|
||||
// while ($row = $res->fetch_assoc()) {
|
||||
// $files[] = $row;
|
||||
// }
|
||||
|
||||
// echo json_encode(['files' => $files], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// $conn->close();
|
||||
?>
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// ✅ 1) Descope 세션 복원 (JWT → $_SESSION['user'])
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/eng/skin/member/basic/descope_session.php';
|
||||
|
||||
// ✅ 2) 관리자 권한 체크
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/eng/bbs/upload_admin_guard.php';
|
||||
// ✅ 관리자만 접근 가능. 일반 사용자는 403 Forbidden
|
||||
|
||||
$host = 'localhost';
|
||||
$user = 'egbim';
|
||||
$pass = 'baron3840!!';
|
||||
$db = 'egbim';
|
||||
|
||||
$conn = new mysqli($host, $user, $pass, $db);
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status'=>'fail','message'=>'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$product_code = $_GET['product_code'] ?? '';
|
||||
$show_deleted = isset($_GET['show_deleted']) ? (int)$_GET['show_deleted'] : 0;
|
||||
|
||||
if (!$product_code) {
|
||||
echo json_encode(['status'=>'fail','message'=>'제품코드 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 삭제 여부 조건 ===
|
||||
$whereDeleted = $show_deleted ? "1=1" : "deleted=0";
|
||||
|
||||
// === 쿼리 ===
|
||||
$sql = "
|
||||
SELECT
|
||||
id, product_code, sw_version, filename, filetype, filesize, filepath,
|
||||
DATE_FORMAT(upload_date, '%Y-%m-%d %H:%i:%s') AS upload_date,
|
||||
uploader,
|
||||
tester,
|
||||
DATE_FORMAT(test_done_date, '%Y-%m-%d %H:%i:%s') AS test_done_date,
|
||||
releaser,
|
||||
DATE_FORMAT(release_date, '%Y-%m-%d %H:%i:%s') AS release_date,
|
||||
deleted
|
||||
FROM deploy_files
|
||||
WHERE product_code = ?
|
||||
AND $whereDeleted
|
||||
ORDER BY sw_version DESC, upload_date ASC
|
||||
";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("s", $product_code);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
|
||||
$files = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$files[] = $row;
|
||||
}
|
||||
|
||||
// ✅ 현재 로그인 관리자 정보 포함
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'files' => $files,
|
||||
'current_user' => [
|
||||
'loginId' => $_SESSION['user']['loginIds'][0] ?? '',
|
||||
'name' => get_upload_admin_display_name(
|
||||
$_SESSION['user']['loginIds'][0] ?? '',
|
||||
$_SESSION['user']['name'] ?? '알 수 없음'
|
||||
)
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -0,0 +1,322 @@
|
||||
<!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">파일 업로드 관리자</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>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<span class="text-sm">내부 배포 버전(최신):</span>
|
||||
<span class="text-blue-600 font-semibold">0.28.1</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>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 영역 -->
|
||||
<div class="flex justify-end space-x-2 mb-4">
|
||||
<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">다운로드</button>
|
||||
<button class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">파일삭제</button>
|
||||
</div>
|
||||
|
||||
<!-- 테이블 -->
|
||||
<div class="bg-white shadow rounded-lg overflow-x-auto">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-gray-200">
|
||||
<tr>
|
||||
<th class="px-4 py-2"><input type="checkbox" id="select-all" /></th>
|
||||
<th class="px-4 py-2">번호</th>
|
||||
<th class="px-4 py-2">파일명</th>
|
||||
<th class="px-4 py-2">파일타입</th>
|
||||
<th class="px-4 py-2">버전</th>
|
||||
<th class="px-4 py-2">크기</th>
|
||||
<th class="px-4 py-2">업로드일</th>
|
||||
<th class="px-4 py-2">업로드자</th>
|
||||
<th class="px-4 py-2">테스트완료</th>
|
||||
<th class="px-4 py-2">테스트자</th>
|
||||
<th class="px-4 py-2">릴리즈일</th>
|
||||
<th class="px-4 py-2">릴리즈자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-list">
|
||||
<!-- JavaScript로 동적 삽입 예정 -->
|
||||
</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">
|
||||
신규 버전 업로드
|
||||
<span class="text-base text-gray-500">(테스트 서버)</span>
|
||||
</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">
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw2" min="0">
|
||||
<span>.</span>
|
||||
<input type="number" class="border w-14 p-1 mx-1 text-center" id="sw3" min="0">
|
||||
<span class="ml-3 text-gray-500">MAJOR</span>
|
||||
</div>
|
||||
|
||||
<!-- 설치파일 -->
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="w-39 flex justify-between items-center">
|
||||
<span>설치파일(EXE)</span><span>:</span>
|
||||
</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-39 flex justify-between items-center">
|
||||
<span>패치파일(EXE)</span><span>:</span>
|
||||
</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-39 flex justify-between items-center">
|
||||
<span>정보파일</span><span>:</span>
|
||||
</label>
|
||||
<input type="file" class="hidden" id="info-file" accept=".zip">
|
||||
<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 class="flex items-center mb-2">
|
||||
<label class="w-39 flex justify-between items-center">
|
||||
<span>릴리즈노트(MD)</span><span>:</span>
|
||||
</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>
|
||||
<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', function() {
|
||||
$('#upload-modal').removeClass('hidden');
|
||||
});
|
||||
// 취소 버튼, 바깥 클릭 → 닫기
|
||||
$('#upload-cancel').on('click', function() {
|
||||
$('#upload-modal').addClass('hidden');
|
||||
});
|
||||
$('#upload-modal').on('click', function(e){
|
||||
if (e.target === this) $(this).addClass('hidden');
|
||||
});
|
||||
|
||||
// 파일 추가 버튼
|
||||
$('#setup-add').on('click', function() { $('#setup-file').click(); });
|
||||
$('#patch-add').on('click', function() { $('#patch-file').click(); });
|
||||
$('#info-add').on('click', function() { $('#info-file').click(); });
|
||||
$('#release-add').on('click', function() { $('#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 || ''); });
|
||||
|
||||
// Presigned 업로드 + DB 저장
|
||||
$('#upload-submit').on('click', async function() {
|
||||
const productCode = $('#product-select').val();
|
||||
const swVersion = [$('#sw1').val(), $('#sw2').val(), $('#sw3').val()].join('.');
|
||||
|
||||
if (!$('#sw1').val() || !$('#sw2').val() || !$('#sw3').val()) {
|
||||
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];
|
||||
|
||||
const files = [];
|
||||
if (setupFile) files.push({name: setupFile.name, type: "setup", size: setupFile.size, file: setupFile});
|
||||
if (patchFile) files.push({name: patchFile.name, type: "patch", size: patchFile.size, file: patchFile});
|
||||
if (infoFile) files.push({name: infoFile.name, type: "info", size: infoFile.size, file: infoFile});
|
||||
if (releaseFile) files.push({name: releaseFile.name, type: "md", size: releaseFile.size, file: releaseFile});
|
||||
|
||||
try {
|
||||
// === 1) Presigned URL 업로드 ===
|
||||
for (let f of files) {
|
||||
let formData = new FormData();
|
||||
formData.append("content_type", f.file.type || "application/octet-stream");
|
||||
formData.append("filename", f.name);
|
||||
|
||||
let presignRes = await fetch("/egbim/sw_upload/upload_presign.php", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
|
||||
// 안전하게 JSON 파싱 (디버깅용 로그 포함)
|
||||
let text = await presignRes.text();
|
||||
let presignData;
|
||||
try {
|
||||
presignData = JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error("❌ JSON 파싱 실패. 원본 응답:", text);
|
||||
throw new Error("presign 응답이 JSON이 아님");
|
||||
}
|
||||
|
||||
let putRes = await fetch(presignData.url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
body: f.file,
|
||||
});
|
||||
if (!putRes.ok) throw new Error(f.name + " 업로드 실패");
|
||||
|
||||
f.path = presignData.key || f.name;
|
||||
}
|
||||
|
||||
// === 2) DB Insert ===
|
||||
$.post("/egbim/sw_upload/upload_popup.php", {
|
||||
product_code: productCode,
|
||||
sw_version: swVersion,
|
||||
is_major: 0,
|
||||
uploader: "송대일",
|
||||
files: JSON.stringify(files)
|
||||
}, function(res){
|
||||
if(res.group){
|
||||
renderFilesGrouped(res.group);
|
||||
alert("업로드 및 DB 저장 성공!");
|
||||
}
|
||||
}, "json");
|
||||
|
||||
// 모달 닫기 + 최신 버전 표시 갱신
|
||||
$('#upload-modal').addClass('hidden');
|
||||
$('.text-blue-600').text(swVersion);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("업로드 중 오류 발생: " + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 테이블 append (버전 묶음)
|
||||
function renderFilesGrouped(groupData) {
|
||||
let rowspan = groupData.files.length;
|
||||
groupData.files.forEach((f, idx) => {
|
||||
$('#file-list').append(`
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2"><input type="checkbox" class="file-checkbox" data-id="${f.id}" /></td>
|
||||
<td class="px-4 py-2">${f.id}</td>
|
||||
<td class="px-4 py-2">${f.filename}</td>
|
||||
<td class="px-4 py-2">${f.filetype}</td>
|
||||
<td class="px-4 py-2">${f.version}</td>
|
||||
<td class="px-4 py-2">${f.size}</td>
|
||||
${idx === 0 ? `
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.upload}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.uploader}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.testdone}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.tester}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.release}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${groupData.releaser}</td>
|
||||
` : ``}
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
loadFileList();
|
||||
});
|
||||
|
||||
function loadFileList(){
|
||||
$.getJSON("/egbim/sw_upload/upload_list.php", function(res){
|
||||
$('#file-list').empty();
|
||||
if(res.files){
|
||||
// 그룹화해서 렌더링
|
||||
renderFilesFromDB(res.files);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderFilesFromDB(files){
|
||||
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)=>{
|
||||
$('#file-list').append(`
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2"><input type="checkbox" /></td>
|
||||
<td class="px-4 py-2">${f.id}</td>
|
||||
<td class="px-4 py-2">${f.setup_filename || f.patch_filename || f.info_filename || f.md_filename}</td>
|
||||
<td class="px-4 py-2">${f.filetype}</td>
|
||||
<td class="px-4 py-2">${f.sw_version}</td>
|
||||
<td class="px-4 py-2">${Math.round(f.filesize/1024/1024*100)/100}MB</td>
|
||||
${idx===0?`
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${f.upload_date}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}">${f.uploader}</td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}"><button class="bg-green-500 text-white px-2 py-1 rounded">완료 확정</button></td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}"></td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}"><button class="bg-blue-500 text-white px-2 py-1 rounded">배포 확정</button></td>
|
||||
<td class="px-4 py-2" rowspan="${rowspan}"></td>
|
||||
`:''}
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,793 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// // DB 연결
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 파라미터 받기 ===
|
||||
// $product_code = $_POST['product_code'] ?? '';
|
||||
// $sw_version = $_POST['sw_version'] ?? '';
|
||||
// $version_type = $_POST['version_type'] ?? 'patch'; // major/minor/patch
|
||||
// $uploader = $_POST['uploader'] ?? '관리자';
|
||||
// $files = isset($_POST['files']) ? json_decode($_POST['files'], true) : [];
|
||||
|
||||
// if (!$product_code || !$sw_version || empty($files)) {
|
||||
// http_response_code(400);
|
||||
// echo json_encode(['message' => '필수값 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// $inserted = [];
|
||||
// foreach ($files as $f) {
|
||||
// $filename = $f['name'] ?? ''; // 규칙 적용된 이름
|
||||
// $original_name = $f['original'] ?? $filename; // 원본 파일명 (없으면 동일)
|
||||
// $filepath = $f['path'] ?? '';
|
||||
// $filetype = $f['type'] ?? '';
|
||||
// $filesize = $f['size'] ?? 0;
|
||||
|
||||
// $sql = "INSERT INTO deploy_files
|
||||
// (product_code, sw_version, version_type,
|
||||
// filename, original_name, filepath, filetype,
|
||||
// filesize, uploader, upload_date, deleted)
|
||||
// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)";
|
||||
|
||||
// $stmt = $conn->prepare($sql);
|
||||
// if (!$stmt) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => '쿼리 준비 실패: '.$conn->error]);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // 바인딩
|
||||
// $stmt->bind_param(
|
||||
// "sssssssis",
|
||||
// $product_code,
|
||||
// $sw_version,
|
||||
// $version_type,
|
||||
// $filename,
|
||||
// $original_name,
|
||||
// $filepath,
|
||||
// $filetype,
|
||||
// $filesize,
|
||||
// $uploader
|
||||
// );
|
||||
|
||||
// if ($stmt->execute()) {
|
||||
// $inserted[] = [
|
||||
// 'id' => $stmt->insert_id,
|
||||
// 'filename' => $filename,
|
||||
// 'original' => $original_name,
|
||||
// 'filetype' => $filetype,
|
||||
// 'version' => $sw_version,
|
||||
// 'size' => round($filesize/1024/1024, 2)."MB"
|
||||
// ];
|
||||
// } else {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB insert 실패: '.$stmt->error]);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// $stmt->close();
|
||||
// }
|
||||
|
||||
|
||||
// /* ✅ 버전 기록: deploy_versions_test 업데이트 */
|
||||
// $check = $conn->prepare("SELECT COUNT(*) FROM deploy_versions_test WHERE product_code=?");
|
||||
// if (!$check) {
|
||||
// error_log("쿼리 준비 실패: ".$conn->error);
|
||||
// } else {
|
||||
// $check->bind_param("s", $product_code);
|
||||
// $check->execute();
|
||||
// $check->bind_result($cnt);
|
||||
// $check->fetch();
|
||||
// $check->close();
|
||||
|
||||
// if ($cnt > 0) {
|
||||
// $upd = $conn->prepare("
|
||||
// UPDATE deploy_versions_test
|
||||
// SET family_version=?, external_version=?, updated_at=NOW()
|
||||
// WHERE product_code=?
|
||||
// ");
|
||||
// if (!$upd) {
|
||||
// error_log("UPDATE 준비 실패: ".$conn->error);
|
||||
// } else {
|
||||
// $upd->bind_param("sss", $sw_version, $sw_version, $product_code);
|
||||
// if (!$upd->execute()) {
|
||||
// error_log("UPDATE 실행 실패: ".$upd->error);
|
||||
// }
|
||||
// $upd->close();
|
||||
// }
|
||||
// } else {
|
||||
// $ins = $conn->prepare("
|
||||
// INSERT INTO deploy_versions_test (product_code, family_version, external_version, updated_at)
|
||||
// VALUES (?, ?, ?, NOW())
|
||||
// ");
|
||||
// if (!$ins) {
|
||||
// error_log("INSERT 준비 실패: ".$conn->error);
|
||||
// } else {
|
||||
// $ins->bind_param("sss", $product_code, $sw_version, $sw_version);
|
||||
// if (!$ins->execute()) {
|
||||
// error_log("INSERT 실행 실패: ".$ins->error);
|
||||
// }
|
||||
// $ins->close();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// echo json_encode([
|
||||
// 'message' => '업로드 DB 기록 완료',
|
||||
// 'group' => [
|
||||
// 'files' => $inserted,
|
||||
// 'upload' => date('Y-m-d H:i:s'),
|
||||
// 'uploader' => $uploader,
|
||||
// 'testdone' => '<button class="bg-green-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
// 'tester' => '',
|
||||
// 'release' => '<button class="bg-blue-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
// 'releaser' => ''
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// $conn->close();
|
||||
?>
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// DB 연결
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 받기 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$version_type = $_POST['version_type'] ?? 'patch'; // major/minor/patch
|
||||
$uploader = $_POST['uploader'] ?? '알 수 없음'; // JS에서 세션스토리지 값
|
||||
$files = isset($_POST['files']) ? json_decode($_POST['files'], true) : [];
|
||||
|
||||
if (!$product_code || !$sw_version || empty($files)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '필수값 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$inserted = [];
|
||||
|
||||
foreach ($files as $f) {
|
||||
$filename = $f['name'] ?? '';
|
||||
$original_name = $f['original'] ?? $filename;
|
||||
$filepath = $f['path'] ?? '';
|
||||
$filetype = $f['type'] ?? '';
|
||||
$filesize = $f['size'] ?? 0;
|
||||
|
||||
$sql = "
|
||||
INSERT INTO deploy_files
|
||||
(product_code, sw_version, version_type,
|
||||
filename, original_name, filepath, filetype,
|
||||
filesize, uploader, upload_date, deleted)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
filename = VALUES(filename),
|
||||
original_name = VALUES(original_name),
|
||||
filepath = VALUES(filepath),
|
||||
filesize = VALUES(filesize),
|
||||
uploader = VALUES(uploader),
|
||||
upload_date = NOW(),
|
||||
deleted = 0
|
||||
";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => '쿼리 준비 실패: '.$conn->error]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt->bind_param(
|
||||
"sssssssis",
|
||||
$product_code,
|
||||
$sw_version,
|
||||
$version_type,
|
||||
$filename,
|
||||
$original_name,
|
||||
$filepath,
|
||||
$filetype,
|
||||
$filesize,
|
||||
$uploader
|
||||
);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$inserted[] = [
|
||||
'filename' => $filename,
|
||||
'original' => $original_name,
|
||||
'filetype' => $filetype,
|
||||
'version' => $sw_version,
|
||||
'size' => round($filesize/1024/1024, 2)."MB"
|
||||
];
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB insert/update 실패: '.$stmt->error]);
|
||||
exit;
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
/* ✅ 버전 기록: deploy_versions_test 업데이트 */
|
||||
$check = $conn->prepare("SELECT COUNT(*) FROM deploy_versions_test WHERE product_code=?");
|
||||
if ($check) {
|
||||
$check->bind_param("s", $product_code);
|
||||
$check->execute();
|
||||
$check->bind_result($cnt);
|
||||
$check->fetch();
|
||||
$check->close();
|
||||
|
||||
if ($cnt > 0) {
|
||||
// ✅ 기존 레코드 있으면 family_version만 갱신
|
||||
$upd = $conn->prepare("
|
||||
UPDATE deploy_versions_test
|
||||
SET family_version=?, updated_at=NOW()
|
||||
WHERE product_code=?
|
||||
");
|
||||
if ($upd) {
|
||||
$upd->bind_param("ss", $sw_version, $product_code);
|
||||
$upd->execute();
|
||||
$upd->close();
|
||||
}
|
||||
} else {
|
||||
// ✅ 신규 insert 시 external_version은 NULL로 둠
|
||||
$ins = $conn->prepare("
|
||||
INSERT INTO deploy_versions_test (product_code, family_version, updated_at)
|
||||
VALUES (?, ?, NOW())
|
||||
");
|
||||
if ($ins) {
|
||||
$ins->bind_param("ss", $product_code, $sw_version);
|
||||
$ins->execute();
|
||||
$ins->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'message' => '업로드 DB 기록 완료 (insert or update)',
|
||||
'group' => [
|
||||
'files' => $inserted,
|
||||
'upload' => date('Y-m-d H:i:s'),
|
||||
'uploader' => $uploader,
|
||||
'testdone' => '<button class="bg-green-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
'tester' => '',
|
||||
'release' => '<button class="bg-blue-500 text-white px-2 py-1 rounded">확인</button>',
|
||||
'releaser' => ''
|
||||
]
|
||||
]);
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Credentials\Credentials;
|
||||
|
||||
// === 버킷/키 설정 ===
|
||||
$bucket_name = "baron-software-test";
|
||||
$account_id = "81fa2d48964d31dd0da9558f9ce601d1";
|
||||
$access_key = "9ade5915f7abd402db60a87f3b3b6d76";
|
||||
$secret_key = "a1a1cfd59f739977f98fb73f214894e39dbac9a39b1f6208020aa2e1303ea54c";
|
||||
|
||||
// === 프론트에서 넘어온 값 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$filename = $_POST['filename'] ?? '';
|
||||
$content_type = $_POST['content_type'] ?? 'application/octet-stream';
|
||||
|
||||
if (!$product_code || !$sw_version || !$filename) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => '필수값 누락 (product_code, sw_version, filename)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 제품 코드 → 실제 폴더명 매핑 ===
|
||||
$map = [
|
||||
"1" => "eg-bim",
|
||||
"2" => "tova",
|
||||
"3" => "gaia",
|
||||
"999" => "test"
|
||||
];
|
||||
|
||||
$folder = $map[$product_code] ?? $product_code; // 혹시 매핑 없으면 그대로 사용
|
||||
|
||||
// === 업로드 경로 ===
|
||||
// ex) eg-bim/1.2.3/setup_eg-bim_1.2.3.exe
|
||||
$key = "{$folder}/{$sw_version}/{$filename}";
|
||||
|
||||
try {
|
||||
$credentials = new Credentials($access_key, $secret_key);
|
||||
|
||||
$s3 = new S3Client([
|
||||
'region' => 'auto',
|
||||
'version' => 'latest',
|
||||
'endpoint' => "https://{$account_id}.r2.cloudflarestorage.com",
|
||||
'credentials' => $credentials,
|
||||
]);
|
||||
|
||||
// Presign URL 생성
|
||||
$cmd = $s3->getCommand('PutObject', [
|
||||
'Bucket' => $bucket_name,
|
||||
'Key' => $key,
|
||||
'ContentType' => $content_type
|
||||
]);
|
||||
|
||||
$request = $s3->createPresignedRequest($cmd, '+1 hour');
|
||||
|
||||
echo json_encode([
|
||||
'url' => (string)$request->getUri(), // 브라우저에서 PUT 할 주소
|
||||
'key' => $key // DB에 저장할 전체 경로
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'error' => 'Presign 실패',
|
||||
'detail' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
// ini_set('display_errors', 1);
|
||||
// ini_set('display_startup_errors', 1);
|
||||
// error_reporting(E_ALL);
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
// require __DIR__ . '/../vendor/autoload.php'; // AWS SDK 로드
|
||||
|
||||
// use Aws\S3\S3Client;
|
||||
// use Aws\Exception\AwsException;
|
||||
|
||||
// // === DB 연결 ===
|
||||
// $conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
// if ($conn->connect_error) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode(['message' => 'DB 연결 실패']);
|
||||
// exit;
|
||||
// }
|
||||
// mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// // === 파라미터 ===
|
||||
// $file_id = $_POST['id'] ?? 0;
|
||||
// $releaser = $_POST['releaser'] ?? '관리자';
|
||||
|
||||
// if (!$file_id) {
|
||||
// http_response_code(400);
|
||||
// echo json_encode(['message' => '필수값 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // === 파일 정보 가져오기 ===
|
||||
// $res = $conn->query("SELECT * FROM deploy_files WHERE id=".(int)$file_id);
|
||||
// $file = $res->fetch_assoc();
|
||||
|
||||
// if (!$file) {
|
||||
// http_response_code(404);
|
||||
// echo json_encode(['message' => '파일 정보 없음']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// $sourceBucket = 'baron-software-test';
|
||||
// $targetBucket = 'baron-software-release';
|
||||
// $objectKey = $file['filepath'];
|
||||
|
||||
// // === S3 클라이언트 생성 ===
|
||||
// // Test 버킷 (Read/Delete)
|
||||
// $testS3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '9ade5915f7abd402db60a87f3b3b6d76', // ✅ write key
|
||||
// 'secret' => 'a1a1cfd59f739977f98fb73f214894e39dbac9a39b1f6208020aa2e1303ea54c',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// // Release 버킷 (Write)
|
||||
// $releaseS3 = new S3Client([
|
||||
// 'version' => 'latest',
|
||||
// 'region' => 'auto',
|
||||
// 'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
// 'credentials' => [
|
||||
// 'key' => '4d0b8feaf0e12873a3d95bd3aa0b787c',
|
||||
// 'secret' => 'dc72a027e5263590f0bc89fc0b5a1e6c846cc4f036642430681364c4a9056ca4',
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
// try {
|
||||
// // 1. 원본 객체 다운로드
|
||||
// $object = $testS3->getObject([
|
||||
// 'Bucket' => $sourceBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 2. Release 버킷에 업로드
|
||||
// $releaseS3->putObject([
|
||||
// 'Bucket' => $targetBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// 'Body' => $object['Body'],
|
||||
// ]);
|
||||
|
||||
// // 3. Test 버킷에서 원본 삭제
|
||||
// $testS3->deleteObject([
|
||||
// 'Bucket' => $sourceBucket,
|
||||
// 'Key' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// // 4. DB 업데이트
|
||||
// $stmt = $conn->prepare("UPDATE deploy_files SET release_date=NOW(), releaser=? WHERE id=?");
|
||||
// $stmt->bind_param("si", $releaser, $file_id);
|
||||
// $stmt->execute();
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => 'ok',
|
||||
// 'message' => '릴리즈 확정 완료',
|
||||
// 'targetKey' => $objectKey,
|
||||
// ]);
|
||||
|
||||
// } catch (AwsException $e) {
|
||||
// http_response_code(500);
|
||||
// echo json_encode([
|
||||
// 'status' => 'fail',
|
||||
// 'message' => '릴리즈 실패: '.$e->getAwsErrorMessage(),
|
||||
// 'error' => $e->getMessage()
|
||||
// ]);
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php'; // AWS SDK 로드
|
||||
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
// === DB 연결 ===
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
// 릴리즈 처리 → releaser를 세션에서 가져옴
|
||||
$releaser = $_POST['releaser'] ?? '알 수 없음'; // JS에서 넘겨준 userName
|
||||
$loginId = $_POST['loginId'] ?? ''; // JS에서 넘겨준 loginId
|
||||
|
||||
if (!$product_code || !$sw_version) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '필수값 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 대상 파일 목록 조회 ===
|
||||
$sql = "SELECT * FROM deploy_files WHERE product_code=? AND sw_version=? AND deleted=0";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("ss", $product_code, $sw_version);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
|
||||
$files = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$files[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
if (empty($files)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['message' => '해당 버전에 대한 파일이 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ 테스트 완료 여부 체크
|
||||
if (empty($files[0]['test_done_date'])) {
|
||||
echo json_encode(['status'=>'fail','message'=>'테스트 완료 후 릴리즈 가능합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 버킷 설정 ===
|
||||
$sourceBucket = 'baron-software-test';
|
||||
$targetBucket = 'baron-software-release';
|
||||
|
||||
$s3 = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'auto',
|
||||
'endpoint' => 'https://81fa2d48964d31dd0da9558f9ce601d1.r2.cloudflarestorage.com',
|
||||
'credentials' => [
|
||||
// ❗ source(read/write) + target(write) 권한이 모두 있는 키
|
||||
'key' => '11d7027f505658acb3aeb40c3955a206',
|
||||
'secret' => '65a78f6c582e0bdfccc2f431fc7b7be2ba11f999f0a8a87142011eac8b84761f',
|
||||
]
|
||||
]);
|
||||
|
||||
$success = [];
|
||||
$failed = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$objectKey = $file['filepath'];
|
||||
|
||||
try {
|
||||
// 1. CopyObject (테스트 → 릴리즈)
|
||||
$s3->copyObject([
|
||||
'Bucket' => $targetBucket,
|
||||
'Key' => $objectKey,
|
||||
'CopySource' => $sourceBucket . '/' . $objectKey,
|
||||
]);
|
||||
|
||||
// 2. 원본 삭제 (테스트 버킷)
|
||||
$s3->deleteObject([
|
||||
'Bucket' => $sourceBucket,
|
||||
'Key' => $objectKey,
|
||||
]);
|
||||
|
||||
$success[] = $objectKey;
|
||||
} catch (AwsException $e) {
|
||||
$failed[] = $objectKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($failed)) {
|
||||
// ❌ 하나라도 실패 → 전체 실패 처리
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '릴리즈 실패: 일부 파일 이동 오류',
|
||||
'failed' => $failed
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ 여기까지 오면 전체 이동 성공
|
||||
// 1) deploy_files 업데이트
|
||||
$stmt = $conn->prepare("
|
||||
UPDATE deploy_files
|
||||
SET release_date = NOW(), releaser = ?
|
||||
WHERE product_code=? AND sw_version=? AND deleted=0
|
||||
");
|
||||
$stmt->bind_param("sss", $releaser, $product_code, $sw_version);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// 2) deploy_versions.family_version 갱신
|
||||
$stmt2 = $conn->prepare("
|
||||
INSERT INTO deploy_versions (product_code, family_version, updated_at)
|
||||
VALUES (?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version = VALUES(family_version),
|
||||
updated_at = NOW()
|
||||
");
|
||||
$stmt2->bind_param("ss", $product_code, $sw_version);
|
||||
$stmt2->execute();
|
||||
$stmt2->close();
|
||||
|
||||
// 최종 응답
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'message' => '릴리즈 확정 완료',
|
||||
'moved' => $success,
|
||||
'release_date' => date('Y-m-d H:i:s'),
|
||||
'releaser' => $releaser
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
|
||||
// DB 연결
|
||||
$conn = new mysqli('localhost', 'egbim', 'baron3840!!', 'egbim');
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 연결 실패']);
|
||||
exit;
|
||||
}
|
||||
mysqli_set_charset($conn, 'utf8mb4');
|
||||
|
||||
// === 파라미터 받기 ===
|
||||
$product_code = $_POST['product_code'] ?? '';
|
||||
$sw_version = $_POST['sw_version'] ?? '';
|
||||
$tester = $_POST['tester'] ?? '알 수 없음';
|
||||
|
||||
if (!$product_code || !$sw_version) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['message' => '필수값 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === 버전 단위 전체 업데이트 ===
|
||||
$sql = "UPDATE deploy_files
|
||||
SET test_done_date = NOW(), tester = ?
|
||||
WHERE sw_version = ?
|
||||
AND product_code = ?"; // 순서 변경
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("sss", $tester, $sw_version, $product_code);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode([
|
||||
'message' => '테스트 완료 업데이트 성공',
|
||||
'test_done_date' => date('Y-m-d H:i:s'),
|
||||
'tester' => $tester
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['message' => 'DB 업데이트 실패: '.$stmt->error]);
|
||||
}
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
"egbim", "baron3840!!",
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$productCode = $_GET['product_code'] ?? '';
|
||||
if (!$productCode) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => '제품코드 없음'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ 내부 최신 버전 & 외부 버전 가져오기
|
||||
$st = $pdo->prepare("
|
||||
SELECT family_version, external_version
|
||||
FROM deploy_versions
|
||||
WHERE product_code=?
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$st->execute([$productCode]);
|
||||
$verRow = $st->fetch();
|
||||
|
||||
$familyVer = $verRow['family_version'] ?? '0.0.0';
|
||||
$externalVer = $verRow['external_version'] ?? '';
|
||||
|
||||
// 👉 내부와 외부가 같으면 "사용안함" 으로 표시되도록 빈 값 처리
|
||||
if ($externalVer === $familyVer) {
|
||||
$externalVer = '';
|
||||
}
|
||||
|
||||
// ✅ 배포 확정된 버전 목록 (콤보박스 옵션용)
|
||||
$st = $pdo->prepare("
|
||||
SELECT DISTINCT sw_version
|
||||
FROM deploy_files
|
||||
WHERE product_code=?
|
||||
AND release_date IS NOT NULL
|
||||
AND deleted=0
|
||||
ORDER BY
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', 1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(sw_version, '.', 2), '.', -1) AS UNSIGNED) DESC,
|
||||
CAST(SUBSTRING_INDEX(sw_version, '.', -1) AS UNSIGNED) DESC
|
||||
");
|
||||
$st->execute([$productCode]);
|
||||
$allVersions = $st->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// ✅ 여기서 필터링
|
||||
$filtered = [];
|
||||
foreach ($allVersions as $v) {
|
||||
// 내부 배포버전보다 높은 버전 제외
|
||||
if (version_compare($v, $familyVer, '>')) continue;
|
||||
|
||||
// 현재 외부배포버전이 지정되어 있으면 그보다 낮은 버전 제외
|
||||
if ($externalVer !== '' && version_compare($v, $externalVer, '<')) continue;
|
||||
|
||||
$filtered[] = $v;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'family_version' => $familyVer,
|
||||
'external_version' => $externalVer, // "" 이면 "사용안함"
|
||||
'external_exists' => ($externalVer !== ''),
|
||||
'confirmed_versions' => $filtered // ✅ 필터링된 버전만 내려줌
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'status' => 'fail',
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// header("Content-Type: application/json; charset=utf-8");
|
||||
// require_once $_SERVER['DOCUMENT_ROOT'].'/egbim/skin/member/basic/descope_session.php';
|
||||
|
||||
// try {
|
||||
// $pdo = new PDO(
|
||||
// "mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
// "egbim", "baron3840!!",
|
||||
// [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
// );
|
||||
|
||||
// $productCode = $_POST['product_code'] ?? '';
|
||||
// $version = $_POST['version'] ?? '';
|
||||
// $userName = $_SESSION['user']['name'] ?? '알수없음';
|
||||
|
||||
// if(!$productCode){
|
||||
// echo json_encode(['status'=>'fail','message'=>'제품 코드 누락']);
|
||||
// exit;
|
||||
// }
|
||||
|
||||
// // ✅ 버전이 비어있으면 기본값 "0.0.0"
|
||||
// $version = trim($version) !== '' ? $version : '0.0.0';
|
||||
|
||||
// // ✅ family_version = external_version 동일하게 저장
|
||||
// $st = $pdo->prepare("
|
||||
// INSERT INTO deploy_versions (product_code, family_version, external_version, external_updated_by, updated_at)
|
||||
// VALUES (:pc, :ver, :ver, :user, NOW())
|
||||
// ON DUPLICATE KEY UPDATE
|
||||
// family_version = VALUES(family_version),
|
||||
// external_version = VALUES(external_version),
|
||||
// external_updated_by = VALUES(external_updated_by),
|
||||
// updated_at = NOW()
|
||||
// ");
|
||||
// $st->execute([
|
||||
// ':pc' => $productCode,
|
||||
// ':ver' => $version,
|
||||
// ':user' => $userName
|
||||
// ]);
|
||||
|
||||
// echo json_encode([
|
||||
// 'status' => 'ok',
|
||||
// 'family_version' => $version,
|
||||
// 'external_version' => $version,
|
||||
// 'updated_by' => $userName
|
||||
// ]);
|
||||
|
||||
// } catch(Exception $e){
|
||||
// echo json_encode(['status'=>'fail','message'=>$e->getMessage()]);
|
||||
// }
|
||||
?>
|
||||
|
||||
<?php
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/eng/skin/member/basic/descope_session.php';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=localhost;dbname=egbim;charset=utf8mb4",
|
||||
"egbim", "baron3840!!",
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$productCode = $_POST['product_code'] ?? '';
|
||||
$version = trim($_POST['version'] ?? '');
|
||||
$userName = $_SESSION['user']['name'] ?? '알수없음';
|
||||
|
||||
if (!$productCode) {
|
||||
echo json_encode(['status'=>'fail','message'=>'제품 코드 누락']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 현재 family_version 가져오기 (내부 최신 버전)
|
||||
$st = $pdo->prepare("SELECT family_version FROM deploy_versions WHERE product_code=? LIMIT 1");
|
||||
$st->execute([$productCode]);
|
||||
$currentFamily = $st->fetchColumn() ?: '0.0.0';
|
||||
|
||||
if ($version === '') {
|
||||
// ✅ 사용안함 → external_version = 내부 배포 버전(family_version)과 동일
|
||||
$externalVer = $currentFamily;
|
||||
} else {
|
||||
// ✅ 외부 버전 직접 지정 → external_version만 변경
|
||||
$externalVer = $version;
|
||||
}
|
||||
|
||||
// DB 업데이트 (family_version은 유지, external_version만 갱신)
|
||||
$st = $pdo->prepare("
|
||||
INSERT INTO deploy_versions (product_code, family_version, external_version, external_updated_by, updated_at)
|
||||
VALUES (:pc, :fam, :ext, :user, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
family_version = VALUES(family_version), -- 기존 값 유지 위해 다시 넣음
|
||||
external_version = VALUES(external_version),
|
||||
external_updated_by = VALUES(external_updated_by),
|
||||
updated_at = NOW()
|
||||
");
|
||||
$st->execute([
|
||||
':pc' => $productCode,
|
||||
':fam' => $currentFamily, // 유지
|
||||
':ext' => $externalVer,
|
||||
':user' => $userName
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'family_version' => $currentFamily,
|
||||
'external_version' => $externalVer,
|
||||
'updated_by' => $userName
|
||||
]);
|
||||
|
||||
} catch(Exception $e){
|
||||
echo json_encode(['status'=>'fail','message'=>$e->getMessage()]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user