daemon-idc init
This commit is contained in:
parent
ae9527dc74
commit
0fd4a82f28
@ -17,15 +17,18 @@ $routes->addPlaceholder('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}
|
||||
$routes->group('cli', ['namespace' => 'App\Controllers\CLI'], function ($routes) {
|
||||
});
|
||||
|
||||
$routes->group('', ['namespace' => 'App\Controllers'], function ($routes) {
|
||||
$routes->get('/', 'Welcome::index');
|
||||
$routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) {
|
||||
$routes->get('login', 'LocalController::login_form');
|
||||
$routes->post('login', 'LocalController::login');
|
||||
$routes->get('google_login', 'GoogleController::login');
|
||||
$routes->get('logout', 'LocalController::logout');
|
||||
$routes->group('', ['namespace' => 'App\Controllers\Front'], function ($routes) {
|
||||
$routes->get('/', 'WelcomeController::index');
|
||||
$routes->group('inquiry', function ($routes) {
|
||||
$routes->post('create', 'InquiryController::create');
|
||||
});
|
||||
});
|
||||
$routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) {
|
||||
$routes->get('login', 'LocalController::login_form');
|
||||
$routes->post('login', 'LocalController::login');
|
||||
$routes->get('google_login', 'GoogleController::login');
|
||||
$routes->get('logout', 'LocalController::logout');
|
||||
});
|
||||
//Admin 관련
|
||||
$routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'authFilter:manager'], function ($routes) {
|
||||
$routes->get('/', 'Welcome::index');
|
||||
@ -59,5 +62,18 @@ $routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'au
|
||||
$routes->get('download/(:alpha)', 'BoardController::download/$1');
|
||||
$routes->get('latest/(:alpha)', 'BoardController::latest/$1');
|
||||
});
|
||||
$routes->group('inquiry', function ($routes) {
|
||||
$routes->get('/', 'InquiryController::index');
|
||||
$routes->get('create', 'InquiryController::create_form');
|
||||
$routes->post('create', 'InquiryController::create');
|
||||
$routes->get('modify/(:num)', 'InquiryController::modify_form/$1');
|
||||
$routes->post('modify/(:num)', 'InquiryController::modify/$1');
|
||||
$routes->get('view/(:num)', 'InquiryController::view/$1');
|
||||
$routes->get('delete/(:num)', 'InquiryController::delete/$1');
|
||||
$routes->get('toggle/(:num)/(:any)', 'InquiryController::toggle/$1/$2');
|
||||
$routes->post('batchjob', 'InquiryController::batchjob');
|
||||
$routes->post('batchjob_delete', 'InquiryController::batchjob_delete');
|
||||
$routes->get('download/(:alpha)', 'InquiryController::download/$1');
|
||||
});
|
||||
});
|
||||
//choi.jh
|
||||
@ -7,8 +7,9 @@ use CodeIgniter\Config\BaseService;
|
||||
//choi.jh
|
||||
use App\Services\Auth\GoogleService;
|
||||
use App\Services\Auth\LocalService;
|
||||
use App\Services\BoardService;
|
||||
use App\Services\UserService;
|
||||
use App\Services\BoardService;
|
||||
use App\Services\InquiryService;
|
||||
//choi.jh
|
||||
|
||||
/**
|
||||
@ -88,5 +89,14 @@ class Services extends BaseService
|
||||
new \App\Models\BoardModel(),
|
||||
);
|
||||
}
|
||||
public static function inquiryservice($getShared = true): InquiryService
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance(__FUNCTION__);
|
||||
}
|
||||
return new InquiryService(
|
||||
new \App\Models\InquiryModel(),
|
||||
);
|
||||
}
|
||||
//choi.jh
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use RuntimeException;
|
||||
use App\Entities\CommonEntity;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use RuntimeException;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use App\Exceptions\FormValidationException;
|
||||
|
||||
/**
|
||||
* AbstractCRUDController
|
||||
@ -12,8 +14,6 @@ use RuntimeException;
|
||||
*/
|
||||
abstract class AbstractCRUDController extends AbstractWebController
|
||||
{
|
||||
// 💡 핵심 1: 각 자식 클래스가 사용할 Entity 클래스 경로를 반환하도록 강제
|
||||
// 이 메서드는 자식 클래스에서 반드시 구현되어야 합니다.
|
||||
// --- 생성 (Create) ---
|
||||
protected function create_form_process(array $formDatas = []): array
|
||||
{
|
||||
@ -39,20 +39,24 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function create_process(array $formDatas): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환
|
||||
$dto = $this->service->createDTO($formDatas);
|
||||
// dd($dto->toArray());
|
||||
|
||||
//DTO 타입 체크 로직을 일반화
|
||||
$dtoClass = $this->service->getDTOClass();
|
||||
if (!$dto instanceof $dtoClass) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . get_class($dto) . "는 사용할 수 없습니다. ({$dtoClass} 필요)");
|
||||
}
|
||||
|
||||
// 💡 여기서 service->create() 내부에서 CommonForm::validate()가 실행되고
|
||||
// 실패 시 FormValidationException이 throw 된다고 가정(권장)
|
||||
return $this->service->create($dto->toArray());
|
||||
}
|
||||
|
||||
protected function create_result_process($entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
protected function create_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process(
|
||||
'info',
|
||||
@ -61,19 +65,61 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
);
|
||||
}
|
||||
|
||||
final public function create(): string|RedirectResponse
|
||||
final public function create(): string|RedirectResponse|ResponseInterface
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
|
||||
$entity = $this->create_process($this->request->getPost());
|
||||
|
||||
// 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사
|
||||
$entityClass = $this->service->getEntityClass();
|
||||
if (!$entity instanceof $entityClass) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능");
|
||||
}
|
||||
|
||||
// ✅ AJAX 요청이면 JSON 성공 응답
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'message' => "{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.",
|
||||
'id' => $entity->getPK(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->create_result_process($entity);
|
||||
|
||||
} catch (FormValidationException $e) {
|
||||
|
||||
// ✅ AJAX 요청이면 422 + 필드별 오류
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response
|
||||
->setStatusCode(422)
|
||||
->setJSON([
|
||||
'ok' => false,
|
||||
'errors' => $e->errors,
|
||||
]);
|
||||
}
|
||||
|
||||
// 기존 redirect 방식 유지
|
||||
return $this->action_redirect_process(
|
||||
'error',
|
||||
static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성 오류:\n" . implode("\n", $e->errors)
|
||||
);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'EXCEPTION_CLASS=' . get_class($e));
|
||||
// ✅ AJAX면 500 JSON
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response
|
||||
->setStatusCode(500)
|
||||
->setJSON([
|
||||
'ok' => false,
|
||||
'message' => static::class . '->' . __FUNCTION__ . "에서 오류:" . $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@ -98,7 +144,6 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
$entity = $this->modify_form_process($uid);
|
||||
$this->addViewDatas('entity', $entity);
|
||||
$action = __FUNCTION__;
|
||||
//FormService에서 필요한 기존 데이터를 $entity에서 추출해서 넘김
|
||||
$this->action_init_process($action, $entity->toArray());
|
||||
return $this->modify_form_result_process($action);
|
||||
} catch (\Throwable $e) {
|
||||
@ -108,7 +153,6 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
|
||||
protected function modify_process($uid, array $formDatas): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환
|
||||
$formDatas[$this->service->getPKField()] = $uid;
|
||||
$dto = $this->service->createDTO($formDatas);
|
||||
//DTO 타입 체크 로직을 일반화
|
||||
@ -119,7 +163,76 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
return $this->service->modify($uid, $dto->toArray());
|
||||
}
|
||||
|
||||
protected function modify_result_process($entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
final public function modify($uid): string|RedirectResponse|ResponseInterface
|
||||
{
|
||||
try {
|
||||
if (!$uid) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
|
||||
$entity = $this->modify_process($uid, $this->request->getPost());
|
||||
|
||||
// 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사
|
||||
$entityClass = $this->service->getEntityClass();
|
||||
if (!$entity instanceof $entityClass) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능");
|
||||
}
|
||||
|
||||
$this->addViewDatas('entity', $entity);
|
||||
|
||||
// ✅ AJAX 요청이면 JSON 성공 응답
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'message' => "{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.",
|
||||
'id' => $entity->getPK(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->modify_result_process($entity);
|
||||
|
||||
} catch (FormValidationException $e) {
|
||||
|
||||
// ✅ AJAX 요청이면 422 + 필드별 오류
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response
|
||||
->setStatusCode(422)
|
||||
->setJSON([
|
||||
'ok' => false,
|
||||
'errors' => $e->errors,
|
||||
]);
|
||||
}
|
||||
|
||||
// 기존 redirect 방식 유지
|
||||
return $this->action_redirect_process(
|
||||
'error',
|
||||
static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정 오류:\n" . implode("\n", $e->errors)
|
||||
);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'EXCEPTION_CLASS=' . get_class($e));
|
||||
// ✅ AJAX면 500 JSON
|
||||
if ($this->request->isAJAX()) {
|
||||
return $this->response
|
||||
->setStatusCode(500)
|
||||
->setJSON([
|
||||
'ok' => false,
|
||||
'message' => static::class . '->' . __FUNCTION__ . "에서 오류:" . $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->action_redirect_process(
|
||||
'error',
|
||||
static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정 오류:" . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function modify_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process(
|
||||
'info',
|
||||
@ -127,31 +240,18 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
$redirect_url ?? '/' . implode('/', [...$this->getActionPaths(), 'view']) . '/' . $entity->getPK()
|
||||
);
|
||||
}
|
||||
final public function modify($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
if (!$uid) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$entity = $this->modify_process($uid, $this->request->getPost());
|
||||
$this->addViewDatas('entity', $entity);
|
||||
return $this->modify_result_process($entity);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- 삭제 (Delete) ---
|
||||
protected function delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
protected function delete_result_process($entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.", $redirect_url);
|
||||
}
|
||||
|
||||
final public function delete($uid): RedirectResponse
|
||||
{
|
||||
try {
|
||||
@ -159,7 +259,6 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
//Delete처리
|
||||
$entity = $this->delete_process($uid);
|
||||
return $this->delete_result_process($entity);
|
||||
} catch (\Throwable $e) {
|
||||
@ -172,20 +271,20 @@ abstract class AbstractCRUDController extends AbstractWebController
|
||||
{
|
||||
return $this->service->getEntity($uid);
|
||||
}
|
||||
|
||||
protected function view_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate'));
|
||||
}
|
||||
|
||||
final public function view($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
if (!$uid) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
//View처리
|
||||
$entity = $this->view_process($uid);
|
||||
$action = __FUNCTION__;
|
||||
//FormService에서 필요한 기존 데이터를 $entity에서 추출해서 넘김
|
||||
$this->action_init_process($action, $entity->toArray());
|
||||
$this->addViewDatas('entity', $entity);
|
||||
return $this->view_result_process($action);
|
||||
|
||||
22
app/Controllers/Admin/InquiryController.php
Normal file
22
app/Controllers/Admin/InquiryController.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class InquiryController extends AdminController
|
||||
{
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('inquiryservice');
|
||||
}
|
||||
$this->addActionPaths('Inquiry');
|
||||
}
|
||||
//Action작업관련
|
||||
//기본 함수 작업
|
||||
//Custom 추가 함수
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Front;
|
||||
|
||||
use App\Controllers\CommonController;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
@ -15,6 +15,7 @@ abstract class FrontController extends CommonController
|
||||
parent::initController($request, $response, $logger);
|
||||
$this->addActionPaths($this->_layout);
|
||||
$this->layouts = LAYOUTS[$this->_layout];
|
||||
helper('util');
|
||||
}
|
||||
protected function action_init_process(string $action, array $formDatas = []): void
|
||||
{
|
||||
31
app/Controllers/Front/InquiryController.php
Normal file
31
app/Controllers/Front/InquiryController.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Front;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class InquiryController extends FrontController
|
||||
{
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('inquiryservice');
|
||||
}
|
||||
$this->addActionPaths('Inquiry');
|
||||
}
|
||||
//Action작업관련
|
||||
//기본 함수 작업
|
||||
//Custom 추가 함수
|
||||
|
||||
protected function create_result_process($entity, ?string $redirect_url = null): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process(
|
||||
'info',
|
||||
"{$this->getTitle()}에서 {$entity->getTitle()} 문의 등록이 완료되었습니다.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Front;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Welcome extends FrontController
|
||||
class WelcomeController extends FrontController
|
||||
{
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
17
app/DTOs/InquiryDTO.php
Normal file
17
app/DTOs/InquiryDTO.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\DTOs;
|
||||
|
||||
class InquiryDTO extends CommonDTO
|
||||
{
|
||||
public ?int $uid = null;
|
||||
public string $title = '';
|
||||
public string $email = '';
|
||||
public string $content = '';
|
||||
public string $status = '';
|
||||
|
||||
public function __construct(array $datas = [])
|
||||
{
|
||||
parent::__construct($datas);
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,35 @@ LOCK TABLES `boardinfo` WRITE;
|
||||
/*!40000 ALTER TABLE `boardinfo` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `inquiryinfo`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `inquiryinfo`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `inquiryinfo` (
|
||||
`uid` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) NOT NULL,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`status` varchar(20) NOT NULL DEFAULT 'available',
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `inquiryinfo`
|
||||
--
|
||||
|
||||
LOCK TABLES `inquiryinfo` WRITE;
|
||||
/*!40000 ALTER TABLE `inquiryinfo` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `inquiryinfo` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `user`
|
||||
--
|
||||
|
||||
22
app/Entities/InquiryEntity.php
Normal file
22
app/Entities/InquiryEntity.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Models\InquiryModel as Model;
|
||||
|
||||
class InquiryEntity extends CommonEntity
|
||||
{
|
||||
const PK = Model::PK;
|
||||
const TITLE = Model::TITLE;
|
||||
protected $attributes = [
|
||||
'title' => '',
|
||||
'email' => '',
|
||||
'status' => '',
|
||||
'content' => ''
|
||||
];
|
||||
public function __construct(array|null $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
}
|
||||
16
app/Exceptions/FormValidationException.php
Normal file
16
app/Exceptions/FormValidationException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FormValidationException extends RuntimeException
|
||||
{
|
||||
public array $errors;
|
||||
|
||||
public function __construct(array $errors, string $message = 'Validation failed', int $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Forms;
|
||||
|
||||
use App\Exceptions\FormValidationException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
@ -13,11 +14,14 @@ use RuntimeException;
|
||||
* 3) validate()에서 dynamicRules 누적 버그 수정 (마지막 규칙만 남는 문제 해결)
|
||||
* 4) "필드 존재 보장"으로 임의 '' 삽입 제거 (미입력 필드가 FK/숫자 규칙을 깨는 문제 방지)
|
||||
* 5) role.* 같은 배열 원소 규칙을 위해 부모 배열 보정 로직 유지/강화
|
||||
*
|
||||
* ✅ 추가:
|
||||
* - validate() 실패 시 RuntimeException(implode) 대신
|
||||
* FormValidationException(errors 배열)을 throw하여
|
||||
* Controller에서 AJAX(422 JSON errors) 응답이 가능하게 함
|
||||
*/
|
||||
abstract class CommonForm
|
||||
{
|
||||
private $_validation = null;
|
||||
|
||||
private array $_attributes = [];
|
||||
private array $_formFields = [];
|
||||
private array $_formRules = [];
|
||||
@ -27,10 +31,11 @@ abstract class CommonForm
|
||||
private array $_formOptions = [];
|
||||
private array $_actionButtons = ['view' => ICONS['SEARCH'], 'delete' => ICONS['DELETE']];
|
||||
private array $_batchjobButtons = ['batchjob' => '일괄처리', 'batchjob_delete' => '일괄삭제'];
|
||||
protected $validation = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$this->_validation = service('validation');
|
||||
$this->validation = service('validation');
|
||||
}
|
||||
|
||||
public function action_init_process(string $action, array &$formDatas = []): void
|
||||
@ -160,7 +165,6 @@ abstract class CommonForm
|
||||
/**
|
||||
* 1) 깊은 배열 구조 정리(배열은 유지)
|
||||
* - 여기서는 null -> '' 같은 변환을 절대 하지 않습니다.
|
||||
* - 이유: FK/숫자/날짜 필드가 ''로 변하면 validation/DB에서 문제가 발생함.
|
||||
*/
|
||||
protected function sanitizeFormDatas($data, string $path = '')
|
||||
{
|
||||
@ -179,11 +183,8 @@ abstract class CommonForm
|
||||
|
||||
/**
|
||||
* 2) 숫자/FK 필드 정규화
|
||||
* - 폼에서 미선택은 보통 ''로 들어옴 -> NULL로 변환
|
||||
* - 숫자 문자열은 int 캐스팅 (선택)
|
||||
*
|
||||
* 주의:
|
||||
* - "빈값을 0으로 취급" 같은 정책이 있다면 여기에서 조정해야 함.
|
||||
* - '' -> null
|
||||
* - 숫자 문자열 -> int
|
||||
*/
|
||||
protected function normalizeNumericEmptyToNull(array $data, array $numericFields): array
|
||||
{
|
||||
@ -245,7 +246,7 @@ abstract class CommonForm
|
||||
$formDatas[$parent] = [];
|
||||
}
|
||||
|
||||
// ✅ 4) 핵심: 배열 원소의 null/'' 제거 + 문자열화(Trim이 null 받지 않도록)
|
||||
// 4) 배열 원소 정리
|
||||
$clean = array_map(
|
||||
fn($v) => is_scalar($v) ? trim((string) $v) : '',
|
||||
$formDatas[$parent]
|
||||
@ -256,14 +257,8 @@ abstract class CommonForm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4) 검증 rule에 따라 "numeric(특히 FK)"로 취급할 필드를 수집
|
||||
* - getFormRule()에서 permit_empty|numeric 로 정의되는 필드를 공통 처리하기 위함
|
||||
*
|
||||
* 구현 전략:
|
||||
* - formRules에서 rule 문자열에 'numeric'가 포함된 필드를 모음
|
||||
* - wildcard(role.*) 제외
|
||||
* 4) 검증 rule에 따라 numeric(FK 포함) 필드 수집
|
||||
*/
|
||||
protected function collectNumericFieldsFromRules(array $formRules): array
|
||||
{
|
||||
@ -276,7 +271,7 @@ abstract class CommonForm
|
||||
continue;
|
||||
}
|
||||
|
||||
// getValidationRule hook 적용 (필드명/룰이 바뀔 수 있으니)
|
||||
// hook 적용
|
||||
[$fieldName, $ruleStr] = $this->getValidationRule($fieldName, (string) $rule);
|
||||
|
||||
if (is_string($ruleStr) && str_contains($ruleStr, 'numeric')) {
|
||||
@ -284,7 +279,6 @@ abstract class CommonForm
|
||||
}
|
||||
}
|
||||
|
||||
// 중복 제거
|
||||
return array_values(array_unique($numericFields));
|
||||
}
|
||||
|
||||
@ -294,20 +288,15 @@ abstract class CommonForm
|
||||
|
||||
/**
|
||||
* 데이터를 검증하고 유효하지 않을 경우 예외를 발생시킵니다.
|
||||
* 2025 CI4 표준: 규칙 배열 내에 label을 포함하여 한글 메시지 출력을 보장합니다.
|
||||
* ✅ 변경점:
|
||||
* - 실패 시 FormValidationException(errors 배열)을 throw
|
||||
* (AJAX에서 422로 내려보내기 위함)
|
||||
*/
|
||||
final public function validate(array &$formDatas): void
|
||||
{
|
||||
log_message('debug', '>>> CommonForm::validate CALLED: ' . static::class);
|
||||
if ($this->_validation === null) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: Validation 서비스가 초기화되지 않았습니다.");
|
||||
}
|
||||
|
||||
try {
|
||||
// 0) 데이터 구조 정리 (null 변환 X)
|
||||
$formDatas = $this->sanitizeFormDatas($formDatas);
|
||||
|
||||
// 1) 필드 라벨/규칙
|
||||
$formFields = $this->getFormFields();
|
||||
$formRules = $this->getFormRules();
|
||||
|
||||
@ -315,61 +304,42 @@ abstract class CommonForm
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다.");
|
||||
}
|
||||
|
||||
// 2) wildcard(role.*) 부모 배열 보정
|
||||
$this->ensureParentArrayForWildcardRules($formDatas, $formRules);
|
||||
|
||||
// 3) numeric(FK 포함) 필드: '' -> null, 숫자 문자열 -> int
|
||||
// (규칙 기반 자동 수집)
|
||||
$numericFields = $this->collectNumericFieldsFromRules($formRules);
|
||||
$formDatas = $this->normalizeNumericEmptyToNull($formDatas, $numericFields);
|
||||
|
||||
// 4) dynamicRules 누적 구성 (버그 수정: 루프마다 초기화 금지)
|
||||
$dynamicRules = [];
|
||||
foreach ($formRules as $field => $rule) {
|
||||
try {
|
||||
// 필드명/규칙 추출(확장 포인트)
|
||||
[$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule);
|
||||
[$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule);
|
||||
|
||||
// label 결정
|
||||
if (isset($formFields[$fieldName])) {
|
||||
$label = $formFields[$fieldName];
|
||||
} elseif (str_contains($fieldName, '.*')) {
|
||||
$parentField = str_replace('.*', '', $fieldName);
|
||||
$label = ($formFields[$parentField] ?? $fieldName) . " 항목";
|
||||
} else {
|
||||
$label = $fieldName;
|
||||
}
|
||||
|
||||
$dynamicRules[$fieldName] = [
|
||||
'label' => $label,
|
||||
'rules' => $ruleStr,
|
||||
];
|
||||
|
||||
// ❌ 존재 보장으로 '' 삽입하지 않음
|
||||
// - required는 CI4가 "키 없음"도 실패 처리 가능(일반적으로)
|
||||
// - permit_empty는 키 없어도 통과 (강제로 '' 만들면 FK/숫자 문제 발생)
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException("유효성 검사 규칙 준비 중 오류 발생 (필드: {$field}): " . $e->getMessage());
|
||||
if (isset($formFields[$fieldName])) {
|
||||
$label = $formFields[$fieldName];
|
||||
} elseif (str_contains($fieldName, '.*')) {
|
||||
$parentField = str_replace('.*', '', $fieldName);
|
||||
$label = ($formFields[$parentField] ?? $fieldName) . ' 항목';
|
||||
} else {
|
||||
$label = $fieldName;
|
||||
}
|
||||
|
||||
$dynamicRules[$fieldName] = [
|
||||
'label' => $label,
|
||||
'rules' => $ruleStr,
|
||||
];
|
||||
}
|
||||
|
||||
$this->_validation->setRules($dynamicRules);
|
||||
$this->validation->setRules($dynamicRules);
|
||||
|
||||
try {
|
||||
if (!$this->_validation->run($formDatas)) {
|
||||
$errors = $this->_validation->getErrors();
|
||||
throw new RuntimeException(implode("\n", $errors));
|
||||
}
|
||||
} catch (\TypeError $e) {
|
||||
throw new RuntimeException("검증 도중 타입 오류 발생: " . $e->getMessage());
|
||||
if (!$this->validation->run($formDatas)) {
|
||||
throw new FormValidationException($this->validation->getErrors());
|
||||
}
|
||||
|
||||
} catch (FormValidationException $e) {
|
||||
throw $e; // ✅ 필드별 errors 유지
|
||||
} catch (\TypeError $e) {
|
||||
throw new RuntimeException('검증 도중 타입 오류 발생: ' . $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
if ($e instanceof RuntimeException) {
|
||||
throw $e;
|
||||
}
|
||||
throw new RuntimeException("유효성 검사 중 시스템 오류 발생: " . $e->getMessage());
|
||||
throw new RuntimeException('유효성 검사 중 시스템 오류 발생: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +347,6 @@ abstract class CommonForm
|
||||
* Overridable hooks
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
// 사용자 정의 hook: 필드/룰 커스터마이즈
|
||||
protected function getValidationRule(string $field, string $rule): array
|
||||
{
|
||||
return [$field, $rule];
|
||||
@ -393,11 +362,6 @@ abstract class CommonForm
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form rule 정의
|
||||
* - permit_empty|numeric 인 FK들이 여기서 정의되면,
|
||||
* validate()에서 자동으로 ''->null 정규화 대상에 포함됩니다.
|
||||
*/
|
||||
public function getFormRule(string $action, string $field, array $formRules): array
|
||||
{
|
||||
switch ($field) {
|
||||
@ -411,32 +375,39 @@ abstract class CommonForm
|
||||
$formRules[$field] = "required|numeric";
|
||||
}
|
||||
break;
|
||||
|
||||
case $this->getAttribute('title_field'):
|
||||
$formRules[$field] = sprintf(
|
||||
"required|trim|string%s",
|
||||
in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : ""
|
||||
);
|
||||
break;
|
||||
|
||||
case "code":
|
||||
$formRules[$field] = sprintf(
|
||||
"required|regex_match[/^[a-zA-Z0-9가-힣\-\_]+$/]|min_length[4]%s",
|
||||
in_array($action, ["create"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : ""
|
||||
);
|
||||
break;
|
||||
|
||||
case "user_uid":
|
||||
$formRules[$field] = "required|numeric";
|
||||
break;
|
||||
|
||||
case "status":
|
||||
$formRules[$field] = "required|trim|string";
|
||||
break;
|
||||
|
||||
case 'picture':
|
||||
$formRules[$field] = "is_image[{$field}]|mime_in[{$field},image/jpg,image/jpeg,image/gif,image/png,image/webp]|max_size[{$field},300]|max_dims[{$field},2048,768]";
|
||||
break;
|
||||
|
||||
case "updated_at":
|
||||
case "created_at":
|
||||
case "deleted_at":
|
||||
$formRules[$field] = "permit_empty|trim|valid_date";
|
||||
break;
|
||||
|
||||
default:
|
||||
$formRules[$field] = "permit_empty|trim|string";
|
||||
break;
|
||||
|
||||
82
app/Forms/InquiryForm.php
Normal file
82
app/Forms/InquiryForm.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Forms;
|
||||
|
||||
use RuntimeException;
|
||||
use App\Forms\CommonForm;
|
||||
|
||||
class InquiryForm extends CommonForm
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
public function action_init_process(string $action, array &$formDatas = []): void
|
||||
{
|
||||
$fields = [
|
||||
'title',
|
||||
'email',
|
||||
'content',
|
||||
'status',
|
||||
];
|
||||
$filters = [
|
||||
'status',
|
||||
];
|
||||
$indexFilter = $filters;
|
||||
$batchjobFilters = ['status'];
|
||||
switch ($action) {
|
||||
case 'view':
|
||||
$fields = [
|
||||
'title',
|
||||
'email',
|
||||
'status',
|
||||
'created_at',
|
||||
'content'
|
||||
];
|
||||
break;
|
||||
case 'index':
|
||||
$fields = [
|
||||
'title',
|
||||
'email',
|
||||
'status',
|
||||
'created_at'
|
||||
];
|
||||
break;
|
||||
case 'download':
|
||||
$fields = [
|
||||
'title',
|
||||
'email',
|
||||
'status',
|
||||
'created_at',
|
||||
'content'
|
||||
];
|
||||
break;
|
||||
}
|
||||
$this->setFormFields($fields);
|
||||
$this->setFormRules($action, $fields);
|
||||
$this->setFormFilters($filters);
|
||||
$this->setFormOptions($action, $filters, $formDatas);
|
||||
$this->setIndexFilters($indexFilter);
|
||||
$this->setBatchjobFilters($batchjobFilters);
|
||||
}
|
||||
|
||||
public function getFormRule(string $action, string $field, array $formRules): array
|
||||
{
|
||||
switch ($field) {
|
||||
case "title":
|
||||
case "content":
|
||||
$formRules[$field] = "required|trim|string";
|
||||
break;
|
||||
case "email":
|
||||
$formRules[$field] = sprintf("required|trim|valid_email%s", in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "");
|
||||
break;
|
||||
case "status":
|
||||
$formRules[$field] = "permit_empty|trim|string";
|
||||
break;
|
||||
default:
|
||||
$formRules = parent::getFormRule($action, $field, $formRules);
|
||||
break;
|
||||
}
|
||||
return $formRules;
|
||||
}
|
||||
}
|
||||
11
app/Helpers/InquiryHelper.php
Normal file
11
app/Helpers/InquiryHelper.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class InquiryHelper extends CommonHelper
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
22
app/Helpers/util_helper.php
Normal file
22
app/Helpers/util_helper.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// 로딩 전체에서 자동 로딩: app/Config/Autoload.php
|
||||
// public $helpers = ['util'];
|
||||
// BaseController에서:
|
||||
// helper('util');
|
||||
|
||||
use App\Traits\UtilTrait;
|
||||
|
||||
if (!function_exists('alertTrait')) {
|
||||
function alertTrait(string $msg, $url = null): string
|
||||
{
|
||||
static $util = null;
|
||||
|
||||
if ($util === null) {
|
||||
$util = new class {
|
||||
use UtilTrait;
|
||||
};
|
||||
}
|
||||
|
||||
return $util->alertTrait($msg, $url);
|
||||
}
|
||||
}
|
||||
19
app/Language/ko/Inquiry.php
Normal file
19
app/Language/ko/Inquiry.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
return [
|
||||
'title' => "문의정보",
|
||||
'label' => [
|
||||
'uid' => "번호",
|
||||
'title' => "제목",
|
||||
'email' => "이메일",
|
||||
'content' => "내용",
|
||||
'status' => "상태",
|
||||
'updated_at' => "수정일",
|
||||
'created_at' => "작성일",
|
||||
'deleted_at' => "삭제일",
|
||||
],
|
||||
"STATUS" => [
|
||||
STATUS['AVAILABLE'] => "문의",
|
||||
STATUS['PAUSE'] => "일시정지",
|
||||
STATUS['TERMINATED'] => "완료",
|
||||
],
|
||||
];
|
||||
@ -9,21 +9,15 @@ abstract class CommonModel extends Model
|
||||
protected $table = '';
|
||||
protected $primaryKey = '';
|
||||
protected $useAutoIncrement = true;
|
||||
// protected $returnType = 'array';
|
||||
//true이면 모든 delete * 메소드 호출은 실제로 행을 삭제하는 것이 아니라 플래그를 데이터베이스로 설정
|
||||
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [];
|
||||
|
||||
// $allowEmptyInserts = false (기본값): 삽입할 데이터가 전혀 없는 경우, CI4는 오류를 발생시키며 쿼리 실행을 막습니다. (보안 및 데이터 무결성 목적)
|
||||
// $allowEmptyInserts = true: 삽입할 데이터가 없어도 INSERT INTO table_name () VALUES () 같은 빈 쿼리 실행을 허용합니다 (극히 드문 경우에 사용).
|
||||
protected bool $allowEmptyInserts = false;
|
||||
protected bool $updateOnlyChanged = true;
|
||||
|
||||
// protected $useEmptyStringIfNull = true; (기본값)
|
||||
// 이 기본 설정 때문에 PHP의 null 값이 데이터베이스로 전달될 때 실제 SQL의 NULL 키워드가 아닌 **빈 문자열 ('')**로 변환되고 있습니다.
|
||||
// 그리고 데이터베이스(MySQL 등)의 설정에 따라 빈 문자열이 업데이트 쿼리에서 무시되거나, 해당 컬럼의 기존 값이 유지되는 현상이 발생합니다.
|
||||
protected $useEmptyStringIfNull = false; //NULL값도 넣을려면 false
|
||||
protected $useEmptyStringIfNull = false; // NULL도 DB로 보내기
|
||||
|
||||
protected array $casts = [];
|
||||
protected array $castHandlers = [];
|
||||
@ -43,64 +37,97 @@ abstract class CommonModel extends Model
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ['emptyStringToNull']; //Field 값이 NULL일 경우 DB Default값 적용용
|
||||
|
||||
/**
|
||||
* ✅ 변경:
|
||||
* - beforeInsert: emptyStringToNull + applyDbDefaultsOnInsert
|
||||
* - beforeUpdate: emptyStringToNull만 유지 (UPDATE에서는 DB default를 쓰려고 컬럼을 빼면 위험/의도와 다름)
|
||||
*/
|
||||
protected $beforeInsert = ['emptyStringToNull', 'applyDbDefaultsOnInsert'];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ['emptyStringToNull']; //Field 값이 NULL일 경우 DB Default값 적용용
|
||||
protected $beforeUpdate = ['emptyStringToNull'];
|
||||
protected $afterUpdate = [];
|
||||
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
protected array $nullableFields = []; // 모델별로 override
|
||||
/**
|
||||
* 빈 문자열을 NULL로 바꾸고 싶은 필드들 (모델별 override)
|
||||
* - 예: FK, 숫자필드 등
|
||||
*/
|
||||
protected array $nullableFields = [];
|
||||
|
||||
/**
|
||||
* ✅ 추가: DB DEFAULT를 “사용하고 싶은” 필드들 (모델별 override)
|
||||
* - INSERT 시 값이 null/''/공백이면 payload에서 제거(unset)해서 DB default가 동작하게 함
|
||||
* - UPDATE에서는 적용하지 않음 (비우기 가능)
|
||||
*/
|
||||
protected array $allowedDbDefaultFields = [];
|
||||
|
||||
/**
|
||||
* 공백문자열도 빈값으로 취급할지 정책
|
||||
*/
|
||||
protected bool $dbDefaultTreatWhitespaceAsEmpty = true;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
final public function getTable(): string
|
||||
{
|
||||
return constant("static::TABLE");
|
||||
}
|
||||
|
||||
final public function getPKField(): string
|
||||
{
|
||||
return constant("static::PK");
|
||||
}
|
||||
|
||||
final public function getTitleField(): string
|
||||
{
|
||||
return constant("static::TITLE");
|
||||
}
|
||||
|
||||
final public function useAutoIncrement(): bool
|
||||
{
|
||||
return $this->useAutoIncrement;
|
||||
}
|
||||
|
||||
final public function getAllowedFields(): array
|
||||
{
|
||||
return $this->allowedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 로직 유지:
|
||||
* - nullableFields에 지정된 필드만 '' => null 로 변환
|
||||
*/
|
||||
protected function emptyStringToNull(array $data): array
|
||||
{
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 공통 모델에서는 아무 필드도 강제하지 않음 (안전)
|
||||
if (empty($this->nullableFields)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->nullableFields as $field) {
|
||||
if (array_key_exists($field, $data['data'])) {
|
||||
$v = $data['data'][$field];
|
||||
if (!array_key_exists($field, $data['data'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 문자열이면 trim 후, 빈문자면 null
|
||||
if (is_string($v)) {
|
||||
$v = trim($v);
|
||||
$data['data'][$field] = ($v === '') ? null : $v;
|
||||
} else {
|
||||
// 문자열이 아닌데도 '' 같은 케이스 방어 (거의 없음)
|
||||
if ($v === '')
|
||||
$data['data'][$field] = null;
|
||||
$v = $data['data'][$field];
|
||||
|
||||
if (is_string($v)) {
|
||||
$v = trim($v);
|
||||
$data['data'][$field] = ($v === '') ? null : $v;
|
||||
} else {
|
||||
if ($v === '') {
|
||||
$data['data'][$field] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,4 +135,50 @@ abstract class CommonModel extends Model
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 추가 로직:
|
||||
* INSERT 때만 DB DEFAULT를 쓰고 싶은 필드를 payload에서 제거(unset)
|
||||
*
|
||||
* - allowedDbDefaultFields에 있는 필드만 처리
|
||||
* - 값이 null / '' / (옵션)공백문자열 이면 unset
|
||||
* - 이렇게 하면 INSERT 쿼리에서 컬럼 자체가 빠져서 DB default가 적용됨
|
||||
*/
|
||||
protected function applyDbDefaultsOnInsert(array $data): array
|
||||
{
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (empty($this->allowedDbDefaultFields)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->allowedDbDefaultFields as $field) {
|
||||
if (!array_key_exists($field, $data['data'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = $data['data'][$field];
|
||||
|
||||
// null이면 제거
|
||||
if ($v === null) {
|
||||
unset($data['data'][$field]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 문자열이면 '' 또는 (옵션)trim 후 '' 이면 제거
|
||||
if (is_string($v)) {
|
||||
if ($v === '') {
|
||||
unset($data['data'][$field]);
|
||||
continue;
|
||||
}
|
||||
if ($this->dbDefaultTreatWhitespaceAsEmpty && trim($v) === '') {
|
||||
unset($data['data'][$field]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
27
app/Models/InquiryModel.php
Normal file
27
app/Models/InquiryModel.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Entities\InquiryEntity;
|
||||
|
||||
class InquiryModel extends CommonModel
|
||||
{
|
||||
const TABLE = "inquiryinfo";
|
||||
const PK = "uid";
|
||||
const TITLE = "title";
|
||||
protected $table = self::TABLE;
|
||||
protected $primaryKey = self::PK;
|
||||
protected $returnType = InquiryEntity::class;
|
||||
protected $allowedFields = [
|
||||
"uid",
|
||||
"title",
|
||||
"email",
|
||||
"content",
|
||||
"status",
|
||||
];
|
||||
protected array $allowedDbDefaultFields = ['status']; // ✅ INSERT에서 status가 비면 DB default 사용
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ use App\Entities\CommonEntity;
|
||||
use App\Models\CommonModel;
|
||||
use App\Libraries\AuthContext;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use App\Exceptions\FormValidationException;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class CommonService
|
||||
@ -41,6 +42,9 @@ abstract class CommonService
|
||||
$result = $callback($db);
|
||||
$db->transComplete();
|
||||
return $result;
|
||||
} catch (FormValidationException $e) {
|
||||
$db->transRollback();
|
||||
throw $e; // ✅ 이거 필수
|
||||
} catch (DatabaseException $e) {
|
||||
$errorMessage = sprintf(
|
||||
"\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n",
|
||||
@ -52,7 +56,7 @@ abstract class CommonService
|
||||
throw new RuntimeException($errorMessage, $e->getCode(), $e);
|
||||
} catch (\Throwable $e) {
|
||||
$db->transRollback();
|
||||
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
|
||||
throw $e; // ✅ 여기서도 RuntimeException으로 감싸지 말 것 (권장)
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,18 +263,13 @@ abstract class CommonService
|
||||
protected function create_process(array $formDatas): CommonEntity
|
||||
{
|
||||
try {
|
||||
log_message('debug', "*** ENTER" . __METHOD__ . " ***");
|
||||
$actionForm = $this->getActionForm();
|
||||
log_message('debug', 'FORMCLASS=' . $this->formClass . ' / FORMINST=' . (is_object($actionForm) ? get_class($actionForm) : 'NULL'));
|
||||
log_message('debug', 'IS_COMMONFORM=' . (is_object($actionForm) && $actionForm instanceof CommonForm ? 'YES' : 'NO'));
|
||||
if ($actionForm instanceof CommonForm) {
|
||||
$actionForm->action_init_process('create', $formDatas);
|
||||
foreach ($formDatas as $field => $value) {
|
||||
$formDatas = $this->action_process_fieldhook($field, $value, $formDatas);
|
||||
}
|
||||
log_message('debug', '>>> BEFORE validate: ' . get_class($actionForm));
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
log_message('debug', '>>> AFTER validate');
|
||||
}
|
||||
$entityClass = $this->getEntityClass();
|
||||
$entity = new $entityClass($formDatas);
|
||||
@ -278,6 +277,8 @@ abstract class CommonService
|
||||
throw new RuntimeException("Return Type은 {$entityClass}만 가능");
|
||||
}
|
||||
return $this->save_process($entity);
|
||||
} catch (FormValidationException $e) {
|
||||
throw $e; // ✅ 감싸지 말고 그대로
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage());
|
||||
}
|
||||
@ -295,18 +296,13 @@ abstract class CommonService
|
||||
protected function modify_process($entity, array $formDatas): CommonEntity
|
||||
{
|
||||
try {
|
||||
log_message('debug', "*** ENTER" . __METHOD__ . " ***");
|
||||
$actionForm = $this->getActionForm();
|
||||
log_message('debug', 'FORMCLASS=' . $this->formClass . ' / FORMINST=' . (is_object($actionForm) ? get_class($actionForm) : 'NULL'));
|
||||
log_message('debug', 'IS_COMMONFORM=' . (is_object($actionForm) && $actionForm instanceof CommonForm ? 'YES' : 'NO'));
|
||||
if ($actionForm instanceof CommonForm) {
|
||||
$actionForm->action_init_process('modify', $formDatas);
|
||||
foreach ($formDatas as $field => $value) {
|
||||
$formDatas = $this->action_process_fieldhook($field, $value, $formDatas);
|
||||
}
|
||||
log_message('debug', '>>> BEFORE validate: ' . get_class($actionForm));
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
log_message('debug', '>>> AFTER validate');
|
||||
}
|
||||
// 검증 통과 후 엔티티 반영
|
||||
$entity->fill($formDatas);
|
||||
@ -314,9 +310,10 @@ abstract class CommonService
|
||||
return $entity;
|
||||
}
|
||||
return $this->save_process($entity);
|
||||
|
||||
} catch (FormValidationException $e) {
|
||||
throw $e; // ✅ 감싸지 말고 그대로
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage() . "\n" . var_export($entity, true));
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
app/Services/InquiryService.php
Normal file
51
app/Services/InquiryService.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\DTOs\InquiryDTO;
|
||||
use App\Forms\InquiryForm;
|
||||
use App\Models\InquiryModel;
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Helpers\InquiryHelper;
|
||||
use App\Entities\InquiryEntity;
|
||||
|
||||
class InquiryService extends CommonService
|
||||
{
|
||||
protected string $formClass = InquiryForm::class;
|
||||
protected string $helperClass = InquiryHelper::class;
|
||||
|
||||
public function __construct(InquiryModel $model)
|
||||
{
|
||||
parent::__construct($model);
|
||||
$this->addClassPaths('Inquiry');
|
||||
}
|
||||
public function getDTOClass(): string
|
||||
{
|
||||
return InquiryDTO::class;
|
||||
}
|
||||
public function createDTO(array $formDatas): InquiryDTO
|
||||
{
|
||||
return new InquiryDTO($formDatas);
|
||||
}
|
||||
public function getEntityClass(): string
|
||||
{
|
||||
return InquiryEntity::class;
|
||||
}
|
||||
//기본 기능부분
|
||||
protected function getEntity_process(mixed $entity): InquiryEntity
|
||||
{
|
||||
return $entity;
|
||||
}
|
||||
//List 검색용
|
||||
//FormFilter 조건절 처리
|
||||
//검색어조건절처리
|
||||
|
||||
//추가기능부분
|
||||
protected function create_process(array $formDatas): CommonEntity
|
||||
{
|
||||
if (!isset($formDatas['status']) || $formDatas['status'] === '' || $formDatas['status'] === null) {
|
||||
$formDatas['status'] = STATUS['AVAILABLE']; // 'available'
|
||||
}
|
||||
return parent::create_process($formDatas);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
|
||||
@ -10,7 +9,8 @@
|
||||
</tr>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%"><?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<th nowrap class="text-end bg-light" width="20%">
|
||||
<?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<td nowrap class="text-start">
|
||||
<?= $viewDatas['helper']->getFieldForm($field, old($field) ?? ($viewDatas['formDatas'][$field] ?? null), $viewDatas) ?>
|
||||
<div><?= validation_show_error($field); ?></div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
|
||||
<div class="layout_top"><?= $this->include("{$viewDatas['layout']['layout']}/top"); ?></div>
|
||||
<table class="layout_middle">
|
||||
<tr>
|
||||
@ -8,37 +8,41 @@
|
||||
<td class="layout_right">
|
||||
<div class="layout_header"><?= $this->include("{$viewDatas['layout']['template']}/index_header"); ?></div>
|
||||
<div id="container" class="layout_content">
|
||||
<link href="/css/<?= $viewDatas['layout']['path'] ?>/index.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="/css/<?= $viewDatas['layout']['path'] ?>/index.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<div class="index_body">
|
||||
<?= $this->include("{$viewDatas['layout']['template']}/index_content_filter"); ?>
|
||||
<?= form_open(current_url(), ['id' => 'batchjob_form', 'method' => "post"]) ?>
|
||||
|
||||
<table class="index_table data table table-bordered table-hover table-striped" data-rtc-resizable-table="reisze_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center bg-light">번호</th>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<th class="text-center bg-light"><?= $viewDatas['helper']->getListLabel($field, $label, $viewDatas) ?></th>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
|
||||
<th class="text-center bg-light"><?= $viewDatas['helper']->getListLabel($field, $label, $viewDatas) ?></th>
|
||||
<?php endforeach ?>
|
||||
<th class="text-center bg-light">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $cnt = 0 ?>
|
||||
<?php foreach ($viewDatas['entities'] as $entity): ?>
|
||||
<?php
|
||||
$viewDatas['entity'] = $entity;
|
||||
$num = $viewDatas['index_totalcount'] - (($viewDatas['page'] - 1) * $viewDatas['perpage'] + $cnt);
|
||||
?>
|
||||
<?php foreach ($viewDatas['entities'] as $entity): ?>
|
||||
<?php
|
||||
$viewDatas['entity'] = $entity;
|
||||
$num = $viewDatas['index_totalcount'] - (($viewDatas['page'] - 1) * $viewDatas['perpage'] + $cnt);
|
||||
?>
|
||||
<tr>
|
||||
<td nowrap><?= $viewDatas['helper']->getListButton('modify', $num, $viewDatas) ?></td>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?><td><?= $viewDatas['helper']->getFieldView($field, $entity->$field, $viewDatas) ?></td><?php endforeach ?>
|
||||
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?><td><?= $viewDatas['helper']->getFieldView($field, $entity->$field, $viewDatas) ?></td><?php endforeach ?>
|
||||
<td nowrap>
|
||||
<?php foreach ($viewDatas['index_actionButtons'] as $action => $label): ?>
|
||||
<?= $viewDatas['helper']->getListButton($action, $label, $viewDatas) ?>
|
||||
<?php foreach ($viewDatas['index_actionButtons'] as $action => $label): ?>
|
||||
<?= $viewDatas['helper']->getListButton($action, $label, $viewDatas) ?>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php $cnt++ ?>
|
||||
<?php $cnt++ ?>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
|
||||
<div id="container" clas
|
||||
s="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
|
||||
<table class="table table-bordered">
|
||||
@ -9,15 +10,15 @@
|
||||
<th class="bg-light" colspan="2"><?= $viewDatas['title'] ?></th>
|
||||
</tr>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%">
|
||||
<?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?>
|
||||
</th>
|
||||
<td nowrap class="text-start">
|
||||
<?= $viewDatas['helper']->getFieldForm($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?>
|
||||
<div><?= validation_show_error($field); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%">
|
||||
<?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?>
|
||||
</th>
|
||||
<td nowrap class="text-start">
|
||||
<?= $viewDatas['helper']->getFieldForm($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?>
|
||||
<div><?= validation_show_error($field); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<div class="text-center"><?= form_submit('', '수정', array("class" => "btn btn-outline btn-primary")); ?></div>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<table class="layout_middle">
|
||||
<tr>
|
||||
<td class="layout_right">
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
|
||||
<div id="container" clas
|
||||
s="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
|
||||
<table class="table table-bordered">
|
||||
@ -9,13 +10,13 @@
|
||||
<th class="bg-light" colspan="2"><?= $viewDatas['title'] ?></th>
|
||||
</tr>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%"><?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<td nowrap class="text-start">
|
||||
<?= $viewDatas['helper']->getFieldForm($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?>
|
||||
<div><?= validation_show_error($field); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%"><?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<td nowrap class="text-start">
|
||||
<?= $viewDatas['helper']->getFieldForm($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?>
|
||||
<div><?= validation_show_error($field); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<div class="text-center"><?= form_submit('', '수정', array("class" => "btn btn-outline btn-primary")); ?></div>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<table class="table table-bordered">
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
<?= $this->extend($viewDatas['layout']['layout']) ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
<div id="container" class="content">
|
||||
|
||||
<div id="container" clas
|
||||
s="content">
|
||||
<div class="form_top"><?= $this->include("{$viewDatas['layout']['template']}/form_content_top"); ?></div>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th class="bg-light" colspan="2"><?= $viewDatas['title'] ?></th>
|
||||
</tr>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%"><?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<td nowrap class="text-start"><?= $viewDatas['helper']->getFieldView($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap class="text-end bg-light" width="20%"><?= $viewDatas['helper']->getFieldLabel($field, $label, $viewDatas) ?></th>
|
||||
<td nowrap class="text-start"><?= $viewDatas['helper']->getFieldView($field, old($field) ?? ($viewDatas['entity']->$field ?? null), $viewDatas) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php if (session('message')): ?><div class="alert alert-danger text-start"><?= nl2br(session('message')) ?></div><?php endif; ?>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="login-page">
|
||||
<div class="login-content">
|
||||
<div class="login-container">
|
||||
<h2 class="login-title">DBMS 로그인</h2>
|
||||
<h2 class="login-title">DaemonIDC 로그인</h2>
|
||||
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
|
||||
<div class="input-group">
|
||||
<label for="userId">아이디</label>
|
||||
@ -15,9 +15,10 @@
|
||||
<div><?= validation_show_error('passwd'); ?></div>
|
||||
</div>
|
||||
<button type="submit" class="btn-login">로그인</button>
|
||||
<div class="login-options"><?= $viewDatas['sns_button'] ?? "" ?></div>
|
||||
<div class="login-options"><?= $viewDatas['sns_button'] ?? "" ?></div>
|
||||
<?= form_close(); ?>
|
||||
<?php if (session('message')): ?><div class="alert alert-info text-start"><?= session('message') ?></div><?php endif; ?>
|
||||
<?php if (session('message')): ?>
|
||||
<div class="alert alert-info text-start"><?= session('message') ?></div><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
26
app/Views/front/welcome/datacenter.php
Normal file
26
app/Views/front/welcome/datacenter.php
Normal file
@ -0,0 +1,26 @@
|
||||
<section id="datacenter" class="py-5 bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-5 order-2 order-lg-1">
|
||||
<img src="https://images.unsplash.com/photo-1596272875729-ed2ff7d6d9c5?auto=format&fit=crop&q=80&w=800"
|
||||
class="img-fluid rounded-4 shadow" alt="IDC Center">
|
||||
</div>
|
||||
<div class="col-lg-7 ps-lg-5 order-1 order-lg-2">
|
||||
<h2 class="fw-bold mb-4">치바/도쿄 프리미엄 IDC</h2>
|
||||
<p class="text-muted mb-5">아시아 비즈니스의 전략적 허브인 치바/도쿄에 위치하여 한국, 일본, 글로벌 트래픽을 가장 낮은 지연시간(Low Latency)으로
|
||||
처리합니다.
|
||||
</p>
|
||||
<div class="row g-4">
|
||||
<div class="col-sm-6">
|
||||
<h5 class="fw-bold"><i class="fa-solid fa-bolt-lightning text-warning me-2"></i> 전력 보장</h5>
|
||||
<p class="small text-muted">N+1 이중화 시스템 설비로 최대 가용성 보장.</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h5 class="fw-bold"><i class="fa-solid fa-snowflake text-info me-2"></i> 공조 시설</h5>
|
||||
<p class="small text-muted">고밀도 서버의 열기를 완벽히 제어하는 기업용 냉방 시스템 완비.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
18
app/Views/front/welcome/hero.php
Normal file
18
app/Views/front/welcome/hero.php
Normal file
@ -0,0 +1,18 @@
|
||||
<section id="home" class="hero-section">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="display-3 fw-bold mb-4">Enterprise-grade <span class="text-primary">Infrastructure</span>
|
||||
</h1>
|
||||
<p class="lead mb-5 text-light opacity-75">
|
||||
High Performance • Secure • Low Latency<br>
|
||||
도쿄 가야바초의 최첨단 IDC에서 제공하는 압도적 성능의 전용 서버 인프라.
|
||||
</p>
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a href="#servers" class="btn btn-primary btn-lg px-5 py-3 fw-bold">서버 사양 보기</a>
|
||||
<a href="#support" class="btn btn-outline-light btn-lg px-5 py-3 fw-bold">상담 및 문의</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -4,302 +4,15 @@
|
||||
<div class="layout_top"><?= $this->include($viewDatas['layout']['layout'] . '/top'); ?></div>
|
||||
<!-- Layout Middle Start -->
|
||||
<!-- Hero Section -->
|
||||
<section id="home" class="hero-section">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="display-3 fw-bold mb-4">Enterprise-grade <span class="text-primary">Infrastructure</span>
|
||||
</h1>
|
||||
<p class="lead mb-5 text-light opacity-75">
|
||||
High Performance • Secure • Low Latency<br>
|
||||
도쿄 가야바초의 최첨단 IDC에서 제공하는 압도적 성능의 전용 서버 인프라.
|
||||
</p>
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a href="#servers" class="btn btn-primary btn-lg px-5 py-3 fw-bold">서버 사양 보기</a>
|
||||
<a href="#support" class="btn btn-outline-light btn-lg px-5 py-3 fw-bold">상담 및 문의</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/hero'); ?>
|
||||
<!-- Stats Bar -->
|
||||
<section class="py-5 bg-white border-bottom">
|
||||
<div class="container text-center">
|
||||
<div class="row g-4">
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-microchip text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">NVMe Gen4</h6>
|
||||
<p class="small text-muted mb-0">엔터프라이즈 전용 SSD</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-shield-halved text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">DDoS Protected</h6>
|
||||
<p class="small text-muted mb-0">실시간 L3/L4/L7 방어</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-bolt text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">100G Backbone</h6>
|
||||
<p class="small text-muted mb-0">글로벌 고속 네트워크</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-headset text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">24/7 Monitoring</h6>
|
||||
<p class="small text-muted mb-0">상주 엔지니어 지원</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/statsbar'); ?>
|
||||
<!-- Server Hosting Section -->
|
||||
<section id="servers" class="py-5 bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="fw-bold mb-3">전용 서버 호스팅</h2>
|
||||
<p class="text-muted">도쿄 IDC 기반의 최신 하드웨어 라인업을 확인하세요.</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
|
||||
<!-- Proxmox 가상 서버 (추가 요청) -->
|
||||
<div class="col">
|
||||
<div class="server-card border-info" style="border-width: 2px;">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title text-info">Proxmox VPS</div>
|
||||
<div class="type-subtitle text-info">고성능 가상 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Hypervisor:</span> <span class="spec-value">Proxmox VE
|
||||
(KVM)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">vCPU:</span> <span class="spec-value">Intel Xeon Gold
|
||||
(Dedicated)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">8GB ~ 64GB (No
|
||||
Overselling)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Storage:</span> <span class="spec-value">Enterprise NVMe
|
||||
SSD</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Feature:</span> <span class="spec-value">Snapshot, Backup, 웹
|
||||
콘솔</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text text-info">가격 문의</div>
|
||||
<button class="btn btn-info btn-inquiry text-white shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- B타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">B타입</div>
|
||||
<div class="type-subtitle text-blue">범용 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (4 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 45만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C타입 (가장 인기) -->
|
||||
<div class="col">
|
||||
<div class="server-card card-featured shadow">
|
||||
<div class="popular-badge">가장 인기</div>
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">C타입</div>
|
||||
<div class="type-subtitle text-purple">고성능 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (8 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text price-purple">월 55만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-purple shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- D타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">D타입</div>
|
||||
<div class="type-subtitle text-blue">프리미엄 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (12 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 65만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">E타입</div>
|
||||
<div class="type-subtitle text-blue">엔터프라이즈 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (20 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">32GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 75만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E2타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">E2타입</div>
|
||||
<div class="type-subtitle text-blue">최상위 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (28 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">32GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 85만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/server'); ?>
|
||||
<!-- Network & DDoS Section -->
|
||||
<section id="network" class="py-5 bg-white">
|
||||
<div class="container py-5">
|
||||
<div class="row align-items-center g-5">
|
||||
<div class="col-lg-6">
|
||||
<h6 class="text-primary fw-bold text-uppercase mb-3">Network & Security</h6>
|
||||
<h2 class="fw-bold mb-4">실시간 지능형 DDoS 방어 시스템</h2>
|
||||
<p class="text-muted mb-4">단순한 트래픽 차단이 아닌, 정교한 데이터 정화 엔진(Scrubbing Center)을 통해 정상적인 비즈니스 트래픽만을 서버로
|
||||
전달합니다.</p>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>L3/L4
|
||||
Mitigation:</strong> UDP/SYN Flood 완벽 대응</span></li>
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>BGP +
|
||||
Anycast:</strong> 전 세계 주요 거점에서 공격 분산 처리</span></li>
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>Always-on
|
||||
Protection:</strong> 1초 미만의 탐지 및 즉각 대응</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="scrubbing-box">
|
||||
<div class="text-center text-white fw-bold mb-4 uppercase tracking-widest">트래픽 흐름도(Traffic Flow)
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-center gap-4">
|
||||
<div class="flow-item w-100">전체 트래픽 (All Internet Traffic)</div>
|
||||
<i class="fa-solid fa-arrow-down opacity-25"></i>
|
||||
<div class="flow-item w-100 bg-primary border-primary text-white shadow-lg">DAEMON Clean Center
|
||||
</div>
|
||||
<i class="fa-solid fa-arrow-down opacity-25"></i>
|
||||
<div class="flow-item w-100 border-success text-success fw-bold">정상 트래픽(Cleaned Data) -> 고객
|
||||
서버(Client Server)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/network'); ?>
|
||||
<!-- Data Center Section -->
|
||||
<section id="datacenter" class="py-5 bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-5 order-2 order-lg-1">
|
||||
<img src="https://images.unsplash.com/photo-1596272875729-ed2ff7d6d9c5?auto=format&fit=crop&q=80&w=800"
|
||||
class="img-fluid rounded-4 shadow" alt="IDC Center">
|
||||
</div>
|
||||
<div class="col-lg-7 ps-lg-5 order-1 order-lg-2">
|
||||
<h2 class="fw-bold mb-4">치바/도쿄 프리미엄 IDC</h2>
|
||||
<p class="text-muted mb-5">아시아 비즈니스의 전략적 허브인 치바/도쿄에 위치하여 한국, 일본, 글로벌 트래픽을 가장 낮은 지연시간(Low Latency)으로
|
||||
처리합니다.
|
||||
</p>
|
||||
<div class="row g-4">
|
||||
<div class="col-sm-6">
|
||||
<h5 class="fw-bold"><i class="fa-solid fa-bolt-lightning text-warning me-2"></i> 전력 보장</h5>
|
||||
<p class="small text-muted">N+1 이중화 시스템 설비로 최대 가용성 보장.</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h5 class="fw-bold"><i class="fa-solid fa-snowflake text-info me-2"></i> 공조 시설</h5>
|
||||
<p class="small text-muted">고밀도 서버의 열기를 완벽히 제어하는 기업용 냉방 시스템 완비.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/datacenter'); ?>
|
||||
<!-- Support Section -->
|
||||
<section id="support" class="py-5 bg-white">
|
||||
<div class="container py-5">
|
||||
@ -323,7 +36,7 @@
|
||||
<i class="fa-brands fa-discord fa-2x text-indigo me-3" style="color: #5865F2;"></i>
|
||||
<div>
|
||||
<div class="fw-bold">Discord 커뮤니티</div>
|
||||
<div class="small text-muted">k6nQg84N</div>
|
||||
<div class="small text-muted">데몬아이디씨</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://discord.com/invite/k6nQg84N" target="blank"
|
||||
@ -331,27 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="card border-0 shadow-sm p-4 p-md-5 rounded-4 bg-light">
|
||||
<form>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">성함 / 업체명</label>
|
||||
<input type="text" class="form-control" placeholder="성함을 입력하세요">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">이메일</label>
|
||||
<input type="email" class="form-control" placeholder="contact@domain.com">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">문의 내용</label>
|
||||
<textarea class="form-control" rows="5" placeholder="문의하실 내용을 상세히 적어주세요."></textarea>
|
||||
</div>
|
||||
<div class="col-12 mt-4">
|
||||
<button type="button" class="btn btn-dark w-100 py-3 fw-bold">문의 접수하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?= $this->include($viewDatas['layout']['path'] . '/welcome/inquiry'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
72
app/Views/front/welcome/inquiry.php
Normal file
72
app/Views/front/welcome/inquiry.php
Normal file
@ -0,0 +1,72 @@
|
||||
<div class="card border-0 shadow-sm p-4 p-md-5 rounded-4 bg-light">
|
||||
<form id="inquiryForm">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">성함 / 업체명</label>
|
||||
<input type="text" id="title" name="title" class="form-control" placeholder="성함을 입력하세요">
|
||||
<div class="error" data-error-for="title"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">이메일</label>
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="예) contact@domain.com">
|
||||
<div class="error" data-error-for="email"></div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">문의 내용</label>
|
||||
<textarea id="content" name="content" class="form-control" rows="5"
|
||||
placeholder="문의하실 내용을 상세히 적어주세요."></textarea>
|
||||
<div class="error" data-error-for="content"></div>
|
||||
</div>
|
||||
<div class="col-12 mt-4">
|
||||
<button type="submit" id="submit" class="btn btn-dark w-100 py-3 fw-bold">문의 접수하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="error text-danger mb-2" data-error-for="_global"></div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('inquiryForm');
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 에러 초기화
|
||||
document.querySelectorAll('.error').forEach(el => el.textContent = '');
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
const res = await fetch('/inquiry/create', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
// 응답이 JSON이 아닐 수도 있으니 안전하게
|
||||
let data = {};
|
||||
try { data = await res.json(); } catch (e) { }
|
||||
|
||||
// ✅ 422: 필드별 에러
|
||||
if (res.status === 422 && data.errors) {
|
||||
Object.entries(data.errors).forEach(([field, msg]) => {
|
||||
const box = document.querySelector(`[data-error-for="${field}"]`);
|
||||
if (box) box.textContent = msg;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 그 외 에러: message를 전역 에러로 표시
|
||||
if (!res.ok || data.ok === false) {
|
||||
const globalBox = document.querySelector(`[data-error-for="_global"]`);
|
||||
if (globalBox) globalBox.textContent = data.message || '처리 중 오류가 발생했습니다.';
|
||||
else alert(data.message || '처리 중 오류가 발생했습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 성공
|
||||
alert('문의가 접수되었습니다.');
|
||||
form.reset();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
39
app/Views/front/welcome/network.php
Normal file
39
app/Views/front/welcome/network.php
Normal file
@ -0,0 +1,39 @@
|
||||
<section id="network" class="py-5 bg-white">
|
||||
<div class="container py-5">
|
||||
<div class="row align-items-center g-5">
|
||||
<div class="col-lg-6">
|
||||
<h6 class="text-primary fw-bold text-uppercase mb-3">Network & Security</h6>
|
||||
<h2 class="fw-bold mb-4">실시간 지능형 DDoS 방어 시스템</h2>
|
||||
<p class="text-muted mb-4">단순한 트래픽 차단이 아닌, 정교한 데이터 정화 엔진(Scrubbing Center)을 통해 정상적인 비즈니스 트래픽만을 서버로
|
||||
전달합니다.</p>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>L3/L4
|
||||
Mitigation:</strong> UDP/SYN Flood 완벽 대응</span></li>
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>BGP +
|
||||
Anycast:</strong> 전 세계 주요 거점에서 공격 분산 처리</span></li>
|
||||
<li class="mb-3 d-flex align-items-start"><i
|
||||
class="fa-solid fa-check-circle text-success me-3 mt-1"></i> <span><strong>Always-on
|
||||
Protection:</strong> 1초 미만의 탐지 및 즉각 대응</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="scrubbing-box">
|
||||
<div class="text-center text-white fw-bold mb-4 uppercase tracking-widest">트래픽 흐름도(Traffic Flow)
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-center gap-4">
|
||||
<div class="flow-item w-100">전체 트래픽 (All Internet Traffic)</div>
|
||||
<i class="fa-solid fa-arrow-down opacity-25"></i>
|
||||
<div class="flow-item w-100 bg-primary border-primary text-white shadow-lg">DAEMON Clean Center
|
||||
</div>
|
||||
<i class="fa-solid fa-arrow-down opacity-25"></i>
|
||||
<div class="flow-item w-100 border-success text-success fw-bold">정상 트래픽(Cleaned Data) -> 고객
|
||||
서버(Client Server)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
191
app/Views/front/welcome/server.php
Normal file
191
app/Views/front/welcome/server.php
Normal file
@ -0,0 +1,191 @@
|
||||
<section id="servers" class="py-5 bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="fw-bold mb-3">전용 서버 호스팅</h2>
|
||||
<p class="text-muted">도쿄 IDC 기반의 최신 하드웨어 라인업을 확인하세요.</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
|
||||
<!-- Proxmox 가상 서버 (추가 요청) -->
|
||||
<div class="col">
|
||||
<div class="server-card border-info" style="border-width: 2px;">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title text-info">Proxmox VPS</div>
|
||||
<div class="type-subtitle text-info">고성능 가상 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Hypervisor:</span> <span class="spec-value">Proxmox VE
|
||||
(KVM)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">vCPU:</span> <span class="spec-value">Intel Xeon Gold
|
||||
(Dedicated)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">8GB ~ 64GB (No
|
||||
Overselling)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Storage:</span> <span class="spec-value">Enterprise NVMe
|
||||
SSD</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">Feature:</span> <span class="spec-value">Snapshot, Backup, 웹
|
||||
콘솔</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-info"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text text-info">가격 문의</div>
|
||||
<button class="btn btn-info btn-inquiry text-white shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- B타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">B타입</div>
|
||||
<div class="type-subtitle text-blue">범용 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (4 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 45만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C타입 (가장 인기) -->
|
||||
<div class="col">
|
||||
<div class="server-card card-featured shadow">
|
||||
<div class="popular-badge">가장 인기</div>
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">C타입</div>
|
||||
<div class="type-subtitle text-purple">고성능 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (8 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-purple"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text price-purple">월 55만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-purple shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- D타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">D타입</div>
|
||||
<div class="type-subtitle text-blue">프리미엄 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (12 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">16GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 65만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">E타입</div>
|
||||
<div class="type-subtitle text-blue">엔터프라이즈 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (20 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">32GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 75만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E2타입 -->
|
||||
<div class="col">
|
||||
<div class="server-card">
|
||||
<div class="card-header-custom">
|
||||
<div class="type-title">E2타입</div>
|
||||
<div class="type-subtitle text-blue">최상위 서버</div>
|
||||
</div>
|
||||
<div class="spec-list">
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">CPU:</span> <span class="spec-value">Xeon (28 Core)</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAM:</span> <span class="spec-value">32GB 이상</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">HDD:</span> <span class="spec-value">SSD 256GB x2 + SSD 500GB
|
||||
x2</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">RAID 구성:</span> <span class="spec-value">RAID 1/1</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">주요 용도:</span> <span class="spec-value">모든 서비스 지원</span></div>
|
||||
<div class="spec-item"><i class="fa-solid fa-check text-success"></i> <span
|
||||
class="spec-label">결제:</span> <span class="spec-value">계좌이체 , 비트코인 가능</span></div>
|
||||
</div>
|
||||
<div class="card-footer-custom">
|
||||
<div class="price-text">월 85만원</div>
|
||||
<button class="btn btn-primary btn-inquiry bg-blue shadow-sm">문의하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
26
app/Views/front/welcome/statsbar.php
Normal file
26
app/Views/front/welcome/statsbar.php
Normal file
@ -0,0 +1,26 @@
|
||||
<section class="py-5 bg-white border-bottom">
|
||||
<div class="container text-center">
|
||||
<div class="row g-4">
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-microchip text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">NVMe Gen4</h6>
|
||||
<p class="small text-muted mb-0">엔터프라이즈 전용 SSD</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-shield-halved text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">DDoS Protected</h6>
|
||||
<p class="small text-muted mb-0">실시간 L3/L4/L7 방어</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-bolt text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">100G Backbone</h6>
|
||||
<p class="small text-muted mb-0">글로벌 고속 네트워크</p>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<i class="fa-solid fa-headset text-primary fa-2x mb-3"></i>
|
||||
<h6 class="fw-bold">24/7 Monitoring</h6>
|
||||
<p class="small text-muted mb-0">상주 엔지니어 지원</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
<head>
|
||||
<title><?= $viewDatas['layout']['title'] ?></title>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?><?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?><?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?> <?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?> <?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
<link href="/css/<?= $viewDatas['layout']['path'] ?>.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="/js/<?= $viewDatas['layout']['path'] ?>.js"></script>
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@ -20,6 +20,6 @@
|
||||
<body>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</body>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
|
||||
</html>
|
||||
@ -1,3 +1,6 @@
|
||||
<div class="accordion-item">
|
||||
<a href="/admin/user"><?= ICONS['MEMBER'] ?> 계정 관리</a>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<a href="/admin/inquiry"><?= ICONS['PHONE'] ?> 문의 관리</a>
|
||||
</div>
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
<head>
|
||||
<title><?= $viewDatas['layout']['title'] ?></title>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?><?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?><?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?> <?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?> <?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
<link href="/css/<?= $viewDatas['layout']['path'] ?>.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="/js/<?= $viewDatas['layout']['path'] ?>.js"></script>
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@ -17,9 +17,9 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php if ($error = session('message')): ?><?= $viewDatas['service']->getHelper()->alertTrait($error) ?><?php endif ?>
|
||||
<div class="middle"><?= $this->renderSection('content') ?></div>
|
||||
<?php if ($error = session('message')): ?> <?= alertTrait($error) ?><?php endif ?>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</body>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
|
||||
</html>
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
<head>
|
||||
<title><?= $viewDatas['layout']['title'] ?></title>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?><?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?><?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['metas'] as $meta): ?> <?= $meta ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['stylesheets'] as $stylesheet): ?> <?= $stylesheet ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['javascripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
<link href="/css/<?= $viewDatas['layout']['path'] ?>.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="/js/<?= $viewDatas['layout']['path'] ?>.js"></script>
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@ -17,9 +17,9 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php if ($error = session('message')): ?><?= $viewDatas['service']->getHelper()->alertTrait($error) ?><?php endif ?>
|
||||
<?= $this->renderSection('content') ?></div>
|
||||
<?php if ($error = session('message')): ?> <?= alertTrait($error) ?><?php endif ?>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</body>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?><?= $javascript ?><?php endforeach; ?>
|
||||
<?php foreach ($viewDatas['layout']['footerScripts'] as $javascript): ?> <?= $javascript ?><?php endforeach; ?>
|
||||
|
||||
</html>
|
||||
@ -6,8 +6,7 @@
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<img src="https://daemon-idc.com/wp-content/uploads/2024/08/cropped-logo-small-30x32.png"
|
||||
height="32" class="me-2 filter-white" style="filter: brightness(0) invert(1);">
|
||||
<span class="h4 mb-0 fw-bold text-white">DAEMON</span>
|
||||
<span class="h4 mb-0 fw-light text-muted ms-1">IDC</span>
|
||||
<span class="h4 mb-0 fw-bold text-white">DAEMON IDC</span>
|
||||
</div>
|
||||
<p class="small lh-lg mb-0">데몬 IDC는 고성능 전용 서버와 엔터프라이즈급 보안 솔루션을 제공하는 글로벌 인프라 전문 브랜드입니다. 도쿄 가야바초의 최첨단 시설에서
|
||||
귀사의 비즈니스를 보호합니다.</p>
|
||||
@ -15,30 +14,33 @@
|
||||
<div class="col-6 col-lg-2">
|
||||
<h6 class="text-white fw-bold mb-4">서비스</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><a href="#">데디케이티드 서버</a></li>
|
||||
<li class="mb-2"><a href="#">전용선 서버</a></li>
|
||||
<li class="mb-2"><a href="#">베어메탈 호스팅</a></li>
|
||||
<li class="mb-2"><a href="#">DDoS 방어</a></li>
|
||||
<li class="mb-2"><a href="#">게임 서버 인프라</a></li>
|
||||
<li class="mb-2"><a href="#">가상 서버 인프라</a></li>
|
||||
<li class="mb-2"><a href="#">VPN 업무용 서버</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6 col-lg-2">
|
||||
<h6 class="text-white fw-bold mb-4">고객지원</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><a href="#">기술 지원</a></li>
|
||||
<li class="mb-2"><a href="#">서비스 이용약관</a></li>
|
||||
<li class="mb-2"><a href="#">개인정보처리방침</a></li>
|
||||
<li class="mb-2"><a href="#">SLA 보장</a></li>
|
||||
<li class="mb-2"><a href="#">전화 상담</a></li>
|
||||
<!-- <li class="mb-2"><a href="#">개인정보처리방침</a></li> -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<h6 class="text-white fw-bold mb-4">Contact Info</h6>
|
||||
<h6 class="text-white fw-bold mb-4">연락처</h6>
|
||||
<ul class="list-unstyled small">
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-solid fa-envelope me-3"></i>
|
||||
sales@daemon-idc.com</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-brands fa-telegram me-3"></i> @daemonidc
|
||||
(Telegram)</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-brands fa-discord me-3"></i> k6nQg84N
|
||||
(Discord)</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-solid fa-phone me-3"></i> 070-8672-0021
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i
|
||||
class="fa-solid fa-envelope me-3"></i>webmaster@daemon-idc.com</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-brands fa-telegram me-3"></i>
|
||||
@daemonidc(Telegram)</li>
|
||||
<li class="mb-3 d-flex align-items-center"><i class="fa-brands fa-discord me-3"></i> 데몬아이디씨(Discord)
|
||||
</li>
|
||||
<li class="mt-4"><span class="badge bg-success text-white px-3 py-2 uppercase fw-bold">System
|
||||
Status: Normal</span></li>
|
||||
</ul>
|
||||
@ -48,7 +50,7 @@
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center small text-muted">
|
||||
<p class="mb-0">© 2024 Daemon IDC. All rights reserved.</p>
|
||||
<div class="d-flex gap-4 mt-3 mt-md-0 fw-bold text-uppercase" style="font-size: 10px; letter-spacing: 2px;">
|
||||
<span>Tokyo, Japan Facility</span>
|
||||
<span>Chiba/Tokyo, Japan Facility</span>
|
||||
<span>Engineering for Excellence</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<a href="https://discord.com/invite/k6nQg84N" class="btn btn-discord px-3 py-2 rounded-3">
|
||||
<i class="fa-brands fa-discord me-2"></i>Discord
|
||||
</a>
|
||||
<a href="#" class="btn btn-dark btn-sm fw-bold px-3 py-2 rounded-3 ms-lg-2">로그인</a>
|
||||
<!-- <a href="#" class="btn btn-dark btn-sm fw-bold px-3 py-2 rounded-3 ms-lg-2">로그인</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,12 @@
|
||||
</span>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<span class="nav-link active" aria-current="page" style="cursor:pointer;"><a
|
||||
href="/admin/customer/client">고객정보</a></span>
|
||||
<span class="nav-link active" aria-current="page" style="cursor:pointer;"><a href="/admin/user">사용자정보</a></span>
|
||||
</li>
|
||||
<li class="nav-item"></li>
|
||||
<span class="nav-link active" aria-current="page" style="cursor:pointer;"><a href="/admin/board">게시판정보</a></span>
|
||||
</li>
|
||||
<li class="nav-item"></li>
|
||||
<span class="nav-link active" aria-current="page" style="cursor:pointer;"><a href="/admin/inquiry">문의정보</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
@ -65,13 +65,20 @@ body {
|
||||
|
||||
/* Hero Section */
|
||||
.hero-section {
|
||||
padding: 160px 0 100px;
|
||||
background: linear-gradient(rgba(15, 23, 42, 0.85), rgba(15, 23, 42, 0.85)),
|
||||
url('https://images.unsplash.com/photo-1558494949-ef010cbdcc48?auto=format&fit=crop&q=80&w=1920');
|
||||
position: relative;
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* 고품질 데이터센터 서버룸 배경 이미지 */
|
||||
background: linear-gradient(rgba(15, 23, 42, 0.8), rgba(15, 23, 42, 0.85)),
|
||||
url('/images/main_background.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-position: center center;
|
||||
background-attachment: scroll;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 100px 0;
|
||||
}
|
||||
|
||||
/* Product Cards */
|
||||
|
||||
BIN
public/images/main_background.png
Normal file
BIN
public/images/main_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
Loading…
Reference in New Issue
Block a user